簡単な例を見てみましょう。おそらく、ロギングの手段を注入しているのでしょう。
クラスを注入する
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
私はそれが何が起こっているかかなり明確だと思います。さらに、IoCコンテナーを使用している場合は、明示的に何かを注入する必要さえなく、コンポジションルートに追加するだけです。
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
デバッグ時にWorker
は、開発者は構成ルートを参照して、使用されている具体的なクラスを判断するだけです。
開発者がより複雑なロジックを必要とする場合、彼は動作するためのインターフェース全体を持っています:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
メソッドの注入
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
まず、コンストラクターパラメーターの名前が長くなっていることに注意してくださいmethodThatLogs
。これが必要なのは、何をすべきかAction<string>
わからないからです。インターフェイスについては完全に明確でしたが、ここではパラメーターの命名に頼らなければなりません。これは本質的に信頼性が低く、ビルド中に実施するのが難しいようです。
さて、このメソッドをどのように注入しますか?まあ、IoCコンテナはあなたのためにそれをしません。したがって、インスタンス化するときに明示的にそれを注入しますWorker
。これにより、いくつかの問題が発生します。
- インスタンス化するのはもっと手間です
Worker
- デバッグしようとする開発者
Worker
は、どの具象インスタンスが呼び出されるのかを把握するのがより難しいことに気付くでしょう。コンポジションのルートだけを調べることはできません。コードをトレースする必要があります。
より複雑なロジックが必要な場合はどうでしょうか?この手法では、1つのメソッドのみが公開されます。今、私はあなたがラムダに複雑なものを焼くことができると思います:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
しかし、単体テストを書いているとき、そのラムダ式をどのようにテストしますか?匿名であるため、ユニットテストフレームワークでは直接インスタンス化できません。賢い方法を見つけられるかもしれませんが、おそらくインターフェースを使用するよりも大きなPITAになるでしょう。
違いの要約:
- メソッドのみを挿入すると、目的を推測することが難しくなりますが、インターフェイスは目的を明確に伝えます。
- メソッドのみをインジェクトすると、インジェクションを受け取るクラスに公開される機能が少なくなります。今日は必要ない場合でも、明日必要になる場合があります。
- IoCコンテナを使用してメソッドのみを自動的に挿入することはできません。
- 特定のインスタンスでどの具象クラスが機能しているかを、コンポジションのルートから知ることはできません。
- ラムダ式自体を単体テストすることは問題です。
上記のすべてに問題がない場合は、メソッドだけを挿入しても構いません。そうでなければ、伝統を守り、インターフェースを注入することをお勧めします。