DIはパターンではなく技術であり、テクノロジーではないことを理解すれば、これは実際には簡単です。
DIコンテナーに依存しない方法でAPIを設計するには、以下の一般的な原則に従います。
実装ではなくインターフェースへのプログラム
この原則は、実際にはDesign Patternsからの(ただし、メモリからの)引用ですが、常にあなたの真の目標である必要があります。DIは、その目的を達成するための手段にすぎません。
ハリウッド原則を適用する
DI用語でのハリウッド原則によると、DIコンテナーを呼び出さないでください。
コード内からコンテナーを呼び出して、依存関係を直接要求しないでください。Constructor Injectionを使用して暗黙的に要求します。
コンストラクターインジェクションを使用する
依存関係が必要な場合は、コンストラクターを介して静的に依存関係を要求します。
public class Service : IService
{
private readonly ISomeDependency dep;
public Service(ISomeDependency dep)
{
if (dep == null)
{
throw new ArgumentNullException("dep");
}
this.dep = dep;
}
public ISomeDependency Dependency
{
get { return this.dep; }
}
}
Serviceクラスがその不変式をどのように保証するかに注意してください。インスタンスが作成されると、Guard句とreadonly
キーワードの組み合わせにより、依存関係が利用できることが保証されます。
寿命の短いオブジェクトが必要な場合は、抽象ファクトリを使用します
コンストラクターインジェクションを使用して注入された依存関係は、存続期間が長くなる傾向がありますが、存続期間の短いオブジェクトが必要な場合や、実行時にのみ既知の値に基づいて依存関係を構成する場合があります。
詳細については、こちらをご覧ください。
最後の責任ある瞬間にのみ作成する
最後までオブジェクトを切り離してください。通常、アプリケーションのエントリポイントですべてを待機して配線できます。これは、コンポジションルートと呼ばれます。
詳細はこちら:
ファサードを使用して簡素化する
結果のAPIが初心者ユーザーにとって複雑すぎると感じる場合は、一般的な依存関係の組み合わせをカプセル化するいくつかのFacadeクラスを常に提供できます。
柔軟なファサードに高度な発見可能性を提供するために、フルーエントビルダーの提供を検討できます。このようなもの:
public class MyFacade
{
private IMyDependency dep;
public MyFacade()
{
this.dep = new DefaultDependency();
}
public MyFacade WithDependency(IMyDependency dependency)
{
this.dep = dependency;
return this;
}
public Foo CreateFoo()
{
return new Foo(this.dep);
}
}
これにより、ユーザーは次のように記述してデフォルトのFooを作成できます。
var foo = new MyFacade().CreateFoo();
ただし、カスタム依存関係を指定できることは非常にわかりやすく、次のように書くことができます。
var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();
MyFacadeクラスが多くの異なる依存関係をカプセル化していると想像すると、拡張性を見つけられるようにしながら、適切なデフォルトを提供する方法が明確であることを願っています。
FWIW、この回答を書いてからずっと後、ここで概念を拡張し、DI-Friendly Librariesに関する長いブログ投稿と、DI-Friendly Frameworksに関する関連記事を書きました。