【问题标题】:An alternative to an array of functions?函数数组的替代方案?
【发布时间】:2014-03-08 15:02:21
【问题描述】:

我正在编写一个应用程序 (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]();
}

问题是这个函数数组结构导致了很多问题,例如我很难从另一个数组函数中调用一个数组函数,例如这不起作用:

"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;
    }
}

但这似乎并不是一个好的做法。或者也许是?

那么,我的替代方案是什么?我应该使用 switch 结构吗?还是有其他更好的解决方案?

【问题讨论】:

  • calling one array function from within another array function 不起作用,因为 $functions 只有在完全评估 RHS 后才会被分配其值。
  • 将函数名存储在变量中,然后将变量用作函数。 $func = $functions[$v];$func();

标签: php arrays function functional-programming


【解决方案1】:

闭包对于 php 中的性能和内存使用来说是昂贵的。程序编码激起了一大堆泥巴,意大利面条编码和其他反模式。开关结构很难测试,它违反了OCP

您应该更喜欢 SOLID 中的 OOP 方式以避免冗余,提高可扩展性和可维护性。这是提供一组可重用的功能的最佳实践。 您还可以将代码分层和模块分开,以降低复杂性并提高可互换性。

在您的情况下,您的类可以实现 __invoke 以将其称为可调用,并且您的键可以是这些类的完全限定名称空间,因此您可以像函数一样调用它。 从现在开始,您还可以使用继承、多态或复合、装饰器等设计模式来重用其他函数或添加函数。

这里是一个简单的例子..

<?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(); 
}

通过实现This和That的特定接口也可以实现此行为。 在迭代中,您可以检查接口以调用定义的合约。或者您构建一个流程类,您可以在其中添加实现此接口的对象。 Process 类可以执行所有附加的对象 (Chain of Responsibility)。

我更喜欢大多数开发人员都可以理解的责任链模式,而不是像 PHP 的 __invoke 拦截器那样神奇。在工厂中,您可以定义您的链,并且您可以定义对链或附加链对象的其他依赖项。

【讨论】:

  • 这是除了示例中的语法错误之外的最佳答案 :) 'do' 不能用于方法名称!
  • Upps .. 我将其重命名为“执行”。谢谢你的提示;)
  • 非常好的答案。题外话,但我使用类似的方法将不同应用程序中的类公开给我为其构建的应用程序。它运行良好,不会浪费内存。
  • 顺便说一句,当 OOP 有其他约定时,为什么要说服人们使用 SOLID,例如GRASP 我应该至少提到一个。 SOLID 在哪些方面比 GRASP 更好,反之亦然?
  • GRASP 是一种非常好的方法,例如领域驱动设计。但我认为 GRASP 或 DDD 需要 SOLID 原则。 GRASP 和 DDD 的目标是降低复杂性并缩短上市时间等。在这种情况下,我指的是 SOLID 以专注于特定的违规行为,以便为解决此问题提供更好的替代方案
【解决方案2】:

你可以用更简洁的方式来做,即将所有这些函数封装到一个类中。它们可以是静态的或动态的,在这种情况下我猜这并不重要。我将展示这两个示例...

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();
}

恕我直言,这比在闭包数组中实现这些函数更干净......而且比实现 switch 更好,因为您仍然必须在循环中调用它 - 那么为什么要放慢另一个 switch 呢?

【讨论】:

  • 动态方法是违反单一职责原则和成长为神级的。静态方法在这里不是全局状态的选项。
  • 我理解您的 cmets 并且我同意您的观点,但另一方面您也应该考虑问题和问题 - 正如您所看到的,问题本身是由错误的架构或错误的方法引起的。无论如何,我找不到解决方案应该与单元测试一起使用的要求,也不应该遵循 SRP。
  • 问题是“..我的替代方案是什么?..还有其他更好的解决方案吗?”。您的解决方案比 Roy 的方法更好,但除了重用功能外,还有相同的问题。添加新功能怎么样?有很多依赖的函数呢?需要参数的函数呢?如果你必须解决这些问题,你必须重构一切。在静态方法中,没有人知道谁能改变状态。我认为,如果问题是由错误的架构引起的,您甚至可以通过良好的设计来解决问题。
  • 将问题转移到一个对象并不是真的更好,而是迈向更好方法的第一步:)
  • 好的,我再次同意,但我提供的解决方案比闭包数组更好,而且仅从问题本身(他当前的设置使用闭包数组)来看,显然不可能拥有并解决依赖关系,也不用不同的参数调用函数。如果他的设置在闭包数组上运行,那么只需稍作修改并更改为一个类就更好了。有时最好在一个类中(在一个地方)拥有 20-30 个方法,然后在不需要解决依赖关系时再拥有 20-30 个类...
【解决方案3】:

我完全赞成Mamuzhis answer 中描述的内容,但我确实想给你一个“快速修复”:

$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
    };
}

我认为这是不言自明的。

【讨论】:

    【解决方案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";
    }
    ?>
    

    或者以更优雅的依赖注入方式,您可以这样做:

    <?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;
            }
        }
        

        【讨论】:

        • Switch 语句很难测试,违反了 OpenClosed 原则。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-26
        • 2012-01-25
        • 2023-03-10
        相关资源
        最近更新 更多