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

ben*_*lal 3 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();
        }
    }
}

?>
Run Code Online (Sandbox Code Playgroud)

您对此实施有何看法?

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

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

Mat*_*oli 6

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

你有2个选择:

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

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

这就是容器.举个例子,这里是PHP-DI - 理解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();
}
Run Code Online (Sandbox Code Playgroud)

它更有意义:打印机打印文件.

这是一个较少的依赖(队列).然后你就可以看到PrintQueueProcessor队列,接收下一个文件并打电话给打印机进行打印.

打印机执行一项任务(打印文件),队列处理器执行一项任务(排队文件以打印它们).