基于字符串的新对象,这是不好的做法?

时间:2014-01-02 20:10:25

标签: php object

考虑以下代码:

public function setLoggers(array $loggers) {
    foreach($loggers as $logger) {
      $logger = new $logger['class']($logger['config']); // This line
      if( ! $logger instanceof LoggerAbstract ) {
        throw new \InvalidArgumentException('Logger '.get_class($logger).' must implement LoggerAbstract');
      }
      $this->loggersInstances[] = $logger;
    }
  }

你会说这是一种不好的做法吗?如果是这样,我可以用其他方式实例化以前不知道其名称的对象吗?

2 个答案:

答案 0 :(得分:3)

你可以做的几件事。

  1. 在其他地方实例化记录器,并将它们直接传递给方法,而不必使用方法内的new关键字。这称为依赖注入。根据您的类,它可能是最佳解决方案(我喜欢将对象实例与业务逻辑分开)。

    基本上:

    public function setLoggers(array $loggers) {
        $this->loggerInstances = $loggers;
        //Maybe some error checking.
    }
    
  2. 使用LoggerFactory,它可以创建记录器并返回它们。这抽象了使用new关键字的需要,并允许接缝更容易测试。当然,工厂是该方法的一个参数,因此也使用依赖注入。

    基本上:

    $logger = $loggerFactory->build($logger["class"], $logger["config"]);
    

    让LoggerFactory拥有new关键字+错误处理等等。


  3. 为什么new关键字的方法难以测试?

    答案简短:

    它将您的方法与外部类紧密结合

    答案越长:

    如果方法中有new,则该方法将链接到要实例化的类,并对其进行实例化。这意味着方法需要知道类。使用工厂时,方法不需要知道类(它知道你要求的名称,但它不需要实际启动类),而是你有一个工厂,他的工作是了解它实例化的对象。

    现在,关于测试,想象一下测试方法:

    public function setLoggersTest() {
        $logHandler->setLoggers(["Logger", "OtherLogger"]);
    }
    

    这实际上要求“Logger”和“OtherLogger”存在于测试上下文中,以便测试通过。这是不可取的。每个测试应该只测试应用程序的一个单元(这就是为什么它被称为“单元”测试)。

    然而,通过并使用工厂

    public function setLoggersTest() {
        $logHandler->setLoggers(["Logger", "OtherLogger"], $mockLoggerFactoryCreatedByTheTest);
    }
    

    我可以在测试中模拟一个工厂,让它返回我想要的任何东西,确保测试不依赖于这个或那个类的存在。

答案 1 :(得分:-2)

我在C#中做过类似的事情。这是一种称为Inversion of Control / Dependency Injection的常见做法。

使用此功能可以使您的代码更灵活,更不可能更改。缺点包括额外的性能损失和复杂性增加。

C#应该是这样。我对PHP不太熟悉,但我认为它应该是类似的。如果您搜索“PHP依赖注入”,您可能会找到您要查找的内容。