我在课堂设计上面临两难选择。 我正在尽力尊重SOLID原则,但我不知道如何处理依赖注入。
这是我的困境:
为了说明我的困境,我尝试创建一个用例。
我想创建一个脚本(我在下面部分实现),它生成一个文件并打印另一个文件:
<?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个参数。
答案 0 :(得分:6)
那么在完整的对象应用程序中应该在哪里创建依赖项呢?在一个只负责依赖实例化的特殊对象中?
您有两个选择:
如果是,该对象的名称是什么以及如何定义它?这就是我们所说的“控制器”吗?
那是容器。举个例子,这里是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
来监视队列,获取下一个文件并调用打印机进行打印。
打印机完成一项工作(打印文件),队列处理器完成一项工作(排队文件以打印它们)。