微依赖性,避免耦合和创建对象的对象

时间:2012-09-09 17:41:44

标签: php oop dependency-injection

  

可能重复:
  Dependency Hell — how does one pass dependencies to deeply nested objects?

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

<?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类切换为其他内容。

但如何避免呢?我唯一想到的是将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");

但这感觉有点hackish。那么你如何处理像这样的微依赖?

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

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

1 个答案:

答案 0 :(得分:3)

由于您的Log对象实际上只是数据传输对象或值对象,因此您可以在Logger类中创建它。在这种情况下这样做是可以的。您不需要将任何内容传递给Logger。但你正确的是you wont be able to mock/stub this easily then.

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

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

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

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

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

如果你不想为此创建一个完整的工厂类,你也可以使用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上有明确的断言,但是如果writer为给定的输入生成write的预期输出,你可以放心地假设Log类型按预期协作。

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