Альтернатива массиву функций?

Я программирую приложение (php), для которого требуется очень длинный список похожих, но разных функций, вызываемых набором ключей:

$functions = [
    "do this" => function() {
        // does this
    },
    "do that" => function() {
        // does that
    }
] 
etc.

Я решил разместить аналогичные функции в массиве, потому что они не похожи на достаточно - получение того же результата с одной большой функцией, которая полна условных утверждений, не сработает. И Мне нужно иметь возможность называть их только клавишей, например:

$program = ["do this", "do that", "do this"];
foreach ($program as $k => $v) {
    $functions[$v]();
}

Thing - это структура функций-массивов, вызывающая множество проблем, например, мне трудно вызвать одну функцию массива из другой функции массива, например. это не работает:

"do that" => function() {
    $functions["do this"]();
}

и это:

"do that" => function() {
    global $functions;
    $functions["do this"]();
}

или это:

"do that" => function($functions) {
    $functions["do this"]();
}

$functions["do that"]($functions);

Я предполагаю, что у меня могла бы быть одна гигантская функция с длинным оператором switch:

function similar_functions($key) {
    switch ($key) {
        case "do this":
            // does this
        break;
        case "do that":
            // does that
        break;
    }
}

Но это действительно не похоже на хорошую практику. Или может быть?

Итак, каковы мои альтернативы? Должен ли я идти с коммутационной структурой? Или есть другое, лучшее решение?

Ответ 1

Я все сторонник того, что Mamuz описывает в его ответ, но я делаю хочу дать вам "быстрое исправление":

$functions = [];

$functions['do this'] = function () {
    // does this
};

$functions['do call'] = function () use ($functions) {
    // calls 'do this'
    $functions['do this']();
}

$functions['do manipulation'] = function () use (&$functions) {
    // manipulates $functions
    $functions['do something else'] = function () {
        // does something else
    };
}

Я думаю, что это довольно понятно.

Ответ 2

Закрытие дорогостоящих для производительности и памяти в php. Процедурное кодирование спровоцировало большой шар грязи, код спагетти и другие анти-шаблоны. Структуры переключателей очень трудно проверить, и это нарушение OCP.

Вы должны предпочесть ООП в SOLID, чтобы избежать избыточности, повышения масштабируемости и сохранения. Это лучшая практика для обеспечения набора функций, которые могут быть использованы повторно. Вы также можете отделить свой код слоями и модулями, чтобы уменьшить сложность и улучшить взаимозаменяемость.

В вашем случае ваши классы могут реализовать __ invoke, чтобы вызвать его как invokable, и ваши ключи могут быть полноквадратичными пространствами имен для этих классов, поэтому вы можете называть его как функцию, С этого момента вы также можете использовать наследование, полиморфизм или шаблоны проектирования, такие как композит, декоратор и т.д. Для повторного использования других функций или для добавления функций.

Вот простой пример.

<?php
use Foo\Bar;

class This
{
    public function __invoke()
    {
        $this->execute();
    }

    public function execute()
    {
        /* ... */
    }
 }

class That extends This
{
    public function execute()
    {
        /* ... */
    }
 }

$namespaces = array("\Foo\Bar\This", "\Foo\Bar\That"); 
foreach ($namespaces as $fullQualifiedNamespace) {
    /** @var callable $fullQualifiedNamespace */
    $fullQualifiedNamespace(); 
}

Это поведение также достигается путем реализации определенного интерфейса с этим и тем. Внутри итерации вы можете проверить интерфейс для вызова определенного контракта. Или вы создаете класс процессов, в котором вы можете добавлять объекты, реализующие этот интерфейс. Класс Process может выполнять все прикрепленные объекты (Цепочка ответственности).

Я бы предпочел, чтобы Chain of Responsibility Pattern был понятен большинству разработчиков и не настолько волшебным, как перехватчик PHP __invoke. В factory вы можете определить свою цепочку, и вы сможете определить другие зависимости к цепочке или прикрепленным объектам цепочки.

Ответ 3

Вы можете сделать это более чистым способом, то есть инкапсулировать все эти функции в класс. Они могут быть статическими или динамическими, в этом случае я не думаю, насколько это возможно. Я покажу оба примера...

1. динамический подход

class MyFunctions
{

    public function doThis()
    {
        /* implement do this here */
    }

    public function doThat()
    {
        /* implement do that here */
    }
    /* ... */
}

/* then somewhere else */
$program = ["doThis", "doThat", "doThis"];
$myFunctions = new MyFunctions;
foreach ($program as $method) {
    $myFunctions->$method();
}

2. статический подход

class MyFunctions
{

    public static function doThis()
    {
        /* implement do this here */
    }

    public static function doThat()
    {
        /* implement do that here */
    }
    /* ... */
}

/* then somewhere else */
$program = ["doThis", "doThat", "doThis"];
foreach ($program as $method) {
    MyFunctions::$method();
}

IMHO это чище, чем реализация этих функций в массиве закрытий... И лучше, чем реализация switch, поскольку вам все равно придется называть это в цикле - так зачем замедляться с помощью другого switch?

Ответ 4

Как насчет старого старого шаблона?;)

<?php

interface Command{
    public function execute();
}

class DoThatCommand implements Command{
    public function execute()
    {
        echo "doing that...";
    }
}

class DoThisCommand implements Command{
    public function execute(){
        echo "doing this...";
        $doThatCommand = new DoThatCommand();
        $doThatCommand->execute();

    }
}

$program = [new DoThatCommand(), new DoThisCommand(), new DoThatCommand()];

foreach ($program as $command) {
    $command->execute();
    echo "\n";
}
?>

или в более элегантном режиме впрыска dependecy вы можете сделать это как:

<?php

interface Command{
    public function execute();
}

class DoThatCommand implements Command{
    public function execute()
    {
        echo "doing that... \n";
    }
}

class DoThisCommand implements Command{
    public function execute(){
        echo "doing this... \n";
    }
}

class DoTheThirdThingCommandThatDependsOnDoThisCommand implements Command{

    public $doThisCommand;

    public function __construct(DoThisCommand $doThisCommand)
    {
        $this->doThisCommand = $doThisCommand; 
    }

    public function execute()
    {
        echo "doing the ThirdThingCommand that depends on DoThisCommand... \n";
        $this->doThisCommand->execute();
    }
}

$command1 = new DoThatCommand(); 
$command2 = new DoThisCommand();
$command3 = new DoTheThirdThingCommandThatDependsOnDoThisCommand($command2);


$program = [$command1, $command2, $command3];
echo "<pre>";
foreach ($program as $command) {
    $command->execute();
}
?>

Ответ 5

Может быть что-то вроде:

/**
 * Save an array with function friendly names and its associated function and params
 */
$functions = array(
    'Do This' => array(
        'fn_name' => 'fn1',
        'fn_args' => array('fn1', 'from fn1')),
    'Do That' => array(
        'fn_name' => 'fn2'
    )
);

/**
 * Call this per each value of the functions array, passing along that function data as parameter
 */
function execute_fn($fn_data) {
    $fn_name = $fn_data['fn_name'];

    if(isset($fn_data['fn_args'])) {
        call_user_func_array($fn_name, $fn_data['fn_args']);
    } else {
        call_user_func($fn_name);
    }


}

function fn1($val1, $val2) {
    echo "I'm being called from $val1 and my second argument value is $val2 \n";
}

function fn2() {
    echo "Doing something for fn2 \n";
    fn1('fn2', 'from fn2');
}

/**
 * Call this to execute the whole set of functions in your functions array
 */
foreach ($functions as $key => $value) {
    execute_fn($value);
}

Таким образом, вы можете определить в $functions массив с именами функций и значениями аргументов, если эта функция ожидает каких-либо.

Ответ 6

Гигантная функция с длинным выражением switch лучше, потому что она позволяет вам называть similar_functions("do this") как часть similar_functions("do that"):

function similar_functions($key) {
    switch ($key) {
        case "do this":
            // does this
        break;
        case "do that":
            similar_functions("do this");
            // and then does that
        break;
    }
}