0

可能重复:
依赖地狱——如何将依赖传递给深度嵌套的对象?

在围绕强依赖注入构建的系统中,我想知道如何处理这样的人为情况:

<?php
class LogWriter
{
    public function write(Log $log)
    {
        echo $log->getMessage();
    }
}

class Log
{
    private $message;
    public function setMessage($message)
    {
        $this->message = $message;
    }
    public function getMessage()
    {
        return $this->message;
    }
}

class Logger
{
    private $writer;
    public function __construct(LogWriter $writer)
    {
        $this->writer = $writer;
    }
    public function write($message)
    {
        // Here is the dependency
        $log = new Log();
        $log->setMessage($message);
        $this->writer->write($log);
    }
}

Logger::write() 方法创建一个 Log 实例,并将其传递给 log writer。我的直觉告诉我这是一个糟糕的方法,从现在开始一个月后,我将追踪一个与之相关的错误,并且我可能想在测试期间将 Log 类切换为其他东西。

但是如何避免呢?唯一想到的是将 Log类型传递给 Logger 构造函数,并将我的 Logger 类更改为:

class Logger
{
    private $writer;
    private $log_type;
    public function __construct(LogWriter $writer, $log_type)
    {
        $this->writer = $writer;
        $this->log_type = $log_type;
    }
    public function write($message)
    {
        $log = new $this->log_type();
        $log->setMessage($message);
        $this->writer->write($log);
    }
}

然后像这样创建一个新的 Logger 实例:

$log_writer = new LogWriter();
$logger = new Logger($log_writer, "Log");

但这感觉有点骇人听闻。那么你如何处理这样的微依赖呢?

注意:我以日志记录类为例,我并不是在寻找解决这个确切问题的方法。我可能只会使用数组而不是 Log 类。

编辑:在更复杂的情况下,我可能会将依赖注入容器传递给 Logger 类,并使用它来创建 Log 的实例,但这对于简单的 logger 类来说似乎过于复杂。

4

1 回答 1

3

由于您的 Log 对象实际上只是一个数据传输对象或值对象,因此您可以在 Logger 类中创建它。在这种情况下可以这样做。您不需要向 Logger 传递任何内容。但是您是对的,因为那时您将无法轻松地模拟/存根。

作为替代方案,如果您想将 Log 类与 Logger 分离,也可以注入 Factory:

$logger = new Logger($logWriter, new LogFactory);

然后从那里创建日志类型:

public function write($message)
{
    $log = $this->logFactory->createNew();
    …

这封装了 Factory 类中的创建逻辑。Factory 内部仍将具有Log硬编码的类型,但工厂拥有它是可以的。然后,您只需在调用时测试它是否返回正确的类型createNew。在您的消费者中,您可以存根该调用。

如果您不想为此创建一个成熟的 Factory 类,您也可以使用 Lambda 代替 Factory。由于它捕获了基本的创建逻辑,因此它实际上与工厂相同,只是没有类:

$logger = new Logger($logWriter, function() { return new Log; });

进而

public function write($message)
{
    $log = call_user_func($this->createLogCallback);
    …

Factoy 和 Lambda 方法都允许在您的单元测试中替换 Log 类型。再说一次,在您的场景中替换 Log 类型似乎没有必要。该Log类型没有它自己的任何依赖项,因此您几乎可以在这里使用真正的交易。您write只需查看LogWriter. 您不会对 Log Mock 进行显式断言,但如果编写器为给定的输入生成预期的输出,则write可以安全地假设该Log类型按预期进行协作。

另请参阅http://misko.hevery.com/2008/09/30/to-new-or-not-to-new了解更多详情。

于 2012-09-09T17:57:49.057 回答