Php 基于字符串的新对象,这是错误的做法吗?

Php 基于字符串的新对象,这是错误的做法吗?,php,object,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 \InvalidArgumentExceptio

考虑以下代码:

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

你会说这是个坏习惯吗?如果是这样的话,还有什么方法可以实例化我以前不知道名称的对象呢?

我在C#中也做过类似的事情。这是一种称为
控制反转
/
依赖项注入
的常见做法

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


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

您可以做一些事情

  • 在别处实例化记录器,并将它们直接传递到方法中,而不必在方法内部使用
    new
    关键字。这就是所谓的依赖注入。根据您的类,它可能是最好的解决方案(我喜欢将对象实例化与业务逻辑分离)

    基本上:

    public function setLoggers(array $loggers) {
        $this->loggerInstances = $loggers;
        //Maybe some error checking.
    }
    
    $logger = $loggerFactory->build($logger["class"], $logger["config"]);
    
  • 使用
    记录器工厂
    ,该工厂能够创建记录器并返回它们。这抽象了使用
    new
    关键字的需要,并允许Seam更容易地进行测试。当然,工厂是方法的参数,因此也使用依赖注入

    基本上:

    public function setLoggers(array $loggers) {
        $this->loggerInstances = $loggers;
        //Maybe some error checking.
    }
    
    $logger = $loggerFactory->build($logger["class"], $logger["config"]);
    
    让LoggerFactory有
    新的
    关键字+错误处理等等


  • 为什么带有
    关键字的方法更难测试?
    简单的回答是:
    它将您的方法与外部类紧密结合

    较长的答案是: 当您的方法中有一个
    新的
    时,该方法将链接到要实例化的类,并对其进行实例化。这意味着方法需要知道类。当使用工厂时,方法不需要知道类(它知道您要求的名称,但不需要实际启动该类),而是有一个工厂,其任务是知道它正在实例化的对象

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

    public function setLoggersTest() {
        $logHandler->setLoggers(["Logger", "OtherLogger"]);
    }
    
    这实际上需要测试上下文中存在“Logger”和“OtherLogger”,测试才能通过。这是不可取的。每个测试应该只测试应用程序的单个单元(这就是为什么它被称为“单元”测试)

    然而,通过路过和使用工厂

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

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

    我认为这是一种不好的做法的唯一方式是,如果用户可以影响
    $logger['class']
    。如果
    $logger
    中的所有元素都由您控制,并且不依赖外部数据,则您应该是安全的。@castis:也许,但“安全”并不意味着“好”,尽管通常情况下相反。这段代码仍然很难测试,也不是很灵活。Krasimir Tsonev在他的教程IoC和DI都不是模式中使用了这一技术。@tereško你能解释一下你说它们不是模式是什么意思吗?如果我理解正确,并且似乎表明它们可以被归类为软件设计模式。如果我弄错了,我真的很想得到一个解释。DI(不要与DI容器混淆)是IoC方法的实际应用。DI不是一种模式,而是一种实践。它甚至不是OOP独有的。@tereško我认为这更像是一个语义问题。我这样想,说的是模式。我在交替使用术语模式/实践/技术,但我可能真的是指实践。我会更新我的答案以避免混淆。谢谢,我想我现在明白多了。我从来没有在OOP之外考虑过DI。小观察:那实际上是一个抽象工厂,因此
    AbstractLoggerFactory
    将是一个更合适的名称。谢谢,我希望能举一些例子说明为什么它更难测试。我是在Yii框架上做这件事的,在config for Logger组件中,我指定了我想要的记录器,这就是为什么它们是字符串格式的(我现在不能实例化)。你是说我应该把它拆分成一个实际创建并返回Logger对象的类吗?Thanks@Jorge:增加解释。基本上,您的方法不应负责创建对象,只应将它们保存在类的属性中(即“设置”属性)。