【问题标题】:Micro-Dependencies, Avoiding Coupling, and Objects Creating Objects [duplicate]微依赖、避免耦合和对象创建对象 [重复]
【发布时间】:2012-09-02 17:03:40
【问题描述】:

可能重复:
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 writer。我的直觉告诉我这是一个糟糕的方法,从现在起一个月后,我将追踪一个与之相关的错误,我可能想在测试期间将 Log 类切换为其他东西。

但是如何避免呢?唯一想到的是将 Log type 传递给 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 类来说似乎过于复杂。

【问题讨论】:

    标签: php oop dependency-injection


    【解决方案1】:

    由于您的 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 类中的创建逻辑。 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 类型没有它自己的任何依赖项,因此您几乎可以在这里使用真正的交易。您只需查看LogWriter 写入的内容即可轻松验证您的write 方法。您不会对 Log Mock 进行显式断言,但如果编写器为 write 的给定输入生成预期的输出,您可以放心地假设 Log 类型按预期进行协作。

    有关详细信息,另请参阅http://misko.hevery.com/2008/09/30/to-new-or-not-to-new

    【讨论】:

    • 问题:你会怎么做?将工厂传递给构造函数,或者只是说,“呃,它只是一个值对象。我可以在这里使用'new'。”?
    • @mellowsoon:做现在最有效的事情——在你的情况下,这可能意味着暂时坚持使用new。不要考虑你将来可能需要什么,you ain't gonna need it
    • @casablanca:你说的太对了。谢谢。
    • @mellowsoon 就我个人而言,我喜欢 Lambda 方法,因为它很简单。再说一次,我认为能够替换 Log 类型没有多大价值,所以我可能只是 new Log。有关详细信息,请参阅末尾添加的部分。
    猜你喜欢
    • 2020-04-06
    • 2015-01-14
    • 1970-01-01
    • 2017-05-12
    • 1970-01-01
    • 2015-07-30
    • 2016-04-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多