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 のインスタンスを作成し、それをログ ライターに渡します。私の直感では、それは悪いアプローチであり、今から 1 か月後に関連するバグを追跡する予定であり、テスト中に 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 のインスタンスを作成することもできますが、単純なロガー クラスの場合は複雑すぎるようです。

4

1 に答える 1

3

Log オブジェクトは実際には単なるデータ転送オブジェクトまたは値オブジェクトであるため、Logger クラス内に作成できます。この場合はそのままで構いません。ロガーに何も渡す必要はありません。しかし、これを簡単にモック/スタブすることはできないという点であなたは正しいです。

別の方法として、Log クラスを Logger から切り離したい場合は、Factory を注入することもできます。

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

そこからログタイプを作成します。

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

これにより、作成ロジックが Factory クラス内にカプセル化されます。Factory はLog内部にハードコーディングされたタイプを保持しますが、Factory がそれを保持してもかまいません。次に、 を呼び出したときに正しい型が返されることをテストするだけですcreateNew。コンシューマーでは、その呼び出しをスタブ化できます。

このために本格的な Factory クラスを作成したくない場合は、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 に答える