Альтернатива массиву функций?
Я программирую приложение (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;
}
}