如何在完整的OO应用程序中处理依赖注入

时间:2014-02-13 15:23:58

标签: php oop dependency-injection solid-principles

我在课堂设计上面临两难选择。 我正在尽力尊重SOLID原则,但我不知道如何处理依赖注入。

这是我的困境:

  • 我认为在类中实例化对象以避免引入依赖是一种不好的做法。那么在完整对象应用程序中应该在哪里创建依赖项呢?在一个只负责依赖实例化的特殊对象中?如果是,该对象的名称是什么以及如何定义它?这就是我们所说的“控制器”吗?
  • 这个“控制器”,对它进行单元测试的正确方法是什么?我们应该进行单元测试吗?
  • 在完整的POO应用程序中,如何避免在类之间传递我们的对象(通常是相同的)?例如,一个DB对象,Log,......这样,我们冒险让构造函数有很多参数,不是吗?

为了说明我的困境,我尝试创建一个用例。

我想创建一个脚本(我在下面部分实现),它生成一个文件并打印另一个文件:

<?php

/**
 * Generate a file, add it to the queue and print the next one
 */
class Script
    public function run() {
        #Generate file
        $fileComputor = new FileComputer(...);
        $file = $fileComputor->compute();

        #Instantiate dependencies for printing
        $db = new Db(new PdoAdapter());
        $printerDriver = new Driver(new HttpRequest(new CurlAdapter()));
        $log = new Log($db);
        $queue = new Queue($db);
        $statsUpdater = new StatsUpdater($db);
        $monitor = new Monitor(new StatsdDriver(new SocketAdapter()));

        #Add generated file to the queue
        $queueModel->push($file);

        #Print the next file on queue if existing
        $printer = new Printer($printerDriver, $log, $queue, $monitor, $statsUpdater);
        $printer->print();
    }
}

class Printer {
    protected $_driver;
    protected $_log;
    protected $_queue;
    protected $_monitor;
    protected $_statsUpdater;

    /**
     * $driver          : Driver used to send documents to the printer
     * $log             : Log actions in database
     * $queue           : Handle the print queue
     * $monitor         : Send metrics to Statsd (to feed the graphit GUI)
     * $statsdUpdater   : Consolidate the statistics database 
     */
    public function __construct($driver, $log, $queue, $monitor, $statsUpdater) {
        $this->_driver = $driver;
        $this->_log = $log;
        $this->_queue = $queue;
        $this->_monitor = $monitor
        $this->_statsUpdater = $statsUpdater;
    }

    public function print() {
        if ($this->_queue->hasNext()) {
            $file = $this->_queue->getNext();

            $this->_driver->print($file);

            $this->_log->log('File has been printed');
            $this->_monitor->sendCount(1);

            $this->_statsUpdater->increment();
        }
    }
}

?>

您对此实施有何看法?

我们想要插入Printer类的每个功能都将导致一个新的依赖项传递给构造函数(例如,我们想要生成一个syslog,来测量打印机处理的时间等)。

在不久的将来,我们将在构造函数调用中包含10到15个参数。

1 个答案:

答案 0 :(得分:6)

  

那么在完整的对象应用程序中应该在哪里创建依赖项呢?在一个只负责依赖实例化的特殊对象中?

您有两个选择:

  • 您可以在应用程序的根目录中自行创建所有对象,例如在前端控制器(index.php)中。如果你的申请有点大,那很快就会成为一个地狱。
  • 您使用依赖注入容器。该对象将负责创建对象(并在构造函数中将它们的依赖项注入它们)。同样在这里:您必须仅在应用程序的根目录中使用/调用容器,例如在前端控制器(index.php)中。
  

如果是,该对象的名称是什么以及如何定义它?这就是我们所说的“控制器”吗?

那是容器。举个例子,这里是PHP-DI - Understanding DI

您可以在控制器上使用依赖注入(我建议这样做):您可以在控制器的构造函数中获得依赖项(就像在任何服务中一样)。有些框架虽然很难(例如Symfony)。

  

这个“控制器”,单元测试的正确方法是什么?我们应该进行单元测试吗?

不是。某些容器允许您配置“工厂”以生成一些对象。

例如,如果创建DBConnection对象很复杂,则可以编写工厂类,该工厂类具有创建DBConnection对象的方法。所以你可以测试工厂类。但我不认为这是必要的。

  

在完整的POO应用程序中,如何避免在类之间传递我们的对象(通常是相同的)?

你永远不应该传递实例,因为你永远不应该调用构造函数:所有对象都是由容器构造的。

所以它变得非常简单:你通过在构造函数中获取依赖项来编写每个类,就是这样。您不关心依赖关系以及这些依赖关系需要什么。

  

例如,一个DB对象,Log,......这样,我们冒险让构造函数有很多参数,不是吗?

是。你说你希望在构造函数中有15-20个参数:这根本不好。

您通常应该尝试最多2-3个参数。有很多意味着你的班级有太多的责任,它对许多事情都有所帮助。

您可以尝试将类的代码拆分为几个较小/更具针对性的类,或者例如使用事件。

如果我们举例,您的打印机可能会像:

public function print($file) {
    $this->driver->print($file);

    $this->log->log('File has been printed');
    $this->monitor->sendCount(1);

    $this->statsUpdater->increment();
}

更有意义:打印机打印文件。

这是一个较少的依赖(队列)。然后你可以有一个PrintQueueProcessor来监视队列,获取下一个文件并调用打印机进行打印。

打印机完成一项工作(打印文件),队列处理器完成一项工作(排队文件以打印它们)。