比較的大きなアプリケーションに依存性注入を実装しようと思っていますが、経験はありません。 UnityやNinjectなど、IoCと利用可能な依存関係インジェクターの概念といくつかの実装を研究しました。しかし、私にはわからないことが一つあります。アプリケーションでインスタンスの作成をどのように整理すればよいですか?
私が考えているのは、いくつかの特定のクラスタイプのオブジェクトを作成するロジックを含むいくつかの特定のファクトリを作成できることです。基本的に、このクラスの静的カーネルインスタンスのNinject Get()メソッドを呼び出すメソッドを持つ静的クラス。
それは私のアプリケーションに依存性注入を実装する正しいアプローチですか、それとも他の原則に従って実装する必要がありますか?
使用するツールについてはまだ考えないでください。 IoCコンテナがなくてもDIを実行できます。
最初のポイント: Mark Seemannは.NetのDIについて非常に優れた本を持っています
第二に:コンポジションルート。セットアップ全体がプロジェクトのエントリポイントで完了していることを確認してください。コードの残りの部分は、使用されているツールについてではなく、インジェクションについて知っている必要があります。
3番目:コンストラクターインジェクションが最も可能性の高い方法です(望まないが、それほど多くはない場合があります)。
4つ目:ラムダファクトリやその他の同様の機能を使用して、インジェクションのみを目的として不要なインターフェイスやクラスを作成しないようにしてください。
質問には2つの部分があります。DIを適切に実装する方法と、DIを使用するように大規模なアプリケーションをリファクタリングする方法です。
最初の部分は@Miyamoto Akira(特にMark Seemannの ".netでの依存性注入"を読むことをお勧めします。Marks blog も優れた無料のリソースです。
2番目の部分はかなり複雑です。
最初のステップとして、すべてのインスタンス化をクラスコンストラクターに移動するだけです。依存関係を挿入せずに、コンストラクターでnew
のみを呼び出すようにします。
これにより、これまでに発生したすべてのSRP違反が強調表示されるため、クラスをより小さな共同作業者に分類できます。
次の問題は、実行時にランタイムパラメータに依存するクラスです。通常、これを修正するには、多くの場合Func<param,type>
を使用して単純なファクトリを作成し、コンストラクタでそれらを初期化して、メソッドでそれらを呼び出します。
次のステップは、依存関係のインターフェースを作成し、これらのインターフェースを除く2番目のコンストラクターをクラスに追加することです。パラメーターなしのコンストラクターは具象インスタンスを更新し、それらを新しいコンストラクターに渡します。これは一般に「B * stard Injection」または「Poor mans DI」と呼ばれます。
これにより、ユニットテストを行うことができ、それがリファクタリングの主な目的である場合は、停止することができます。新しいコードはコンストラクターインジェクションで記述されますが、古いコードは記述どおりに機能し続けますが、テストは可能です。
もちろん、さらに先へ進むこともできます。 IOCコンテナーを使用する場合、次のステップは、パラメーターなしのコンストラクターでのnew
へのすべての直接呼び出しを、IOCへの静的呼び出しに置き換えることです。コンテナー、本質的に(ab)サービスロケーターとして使用します。
これにより、以前と同様に処理するランタイムコンストラクターパラメーターのケースが増えます。
これが完了すると、パラメーターのないコンストラクターを削除し、純粋なDIにリファクタリングできます。
結局これは大変な作業になるので、なぜそれをしたいのかを決定し、リファクタリングから最大の恩恵を受けるコードベースの部分に優先順位を付けてください。
最初に、新しいプロジェクトを開始するのではなく、既存のプロジェクトをリファクタリングすることで、これを大幅に難しくしていることを述べておきます。
大規模なアプリケーションだと言ったので、最初は小さなコンポーネントを選びます。ほかで使用されていない「リーフノード」コンポーネントが望ましい。このアプリケーションの自動テストの状態はわかりませんが、このコンポーネントのすべての単体テストを中断することになります。そのために準備してください。ステップ0は、まだ存在しない場合に変更するコンポーネントの統合テストを作成しています。最後の手段(テストインフラストラクチャなし、それを記述するための賛同を得ない)として、このコンポーネントが機能していることを確認するために実行できる一連の手動テストを考え出します。
DIリファクタリングの目標を述べる最も簡単な方法は、このコンポーネントから「new」演算子のすべてのインスタンスを削除することです。これらは通常、次の2つのカテゴリに分類されます。
不変メンバー変数:これらは(通常はコンストラクターで)一度設定され、オブジェクトの存続期間中は再割り当てされない変数です。これらについては、オブジェクトのインスタンスをコンストラクターに注入できます。通常、これらのオブジェクトを破棄する責任はありません(ここでは決して言いたくないのですが、実際にその責任を負うべきではありません)。
バリアントメンバー変数/メソッド変数:これらは、オブジェクトの存続期間中のある時点でガベージコレクションされる変数です。これらの場合、クラスにファクトリーを注入して、これらのインスタンスを提供する必要があります。ファクトリによって作成されたオブジェクトを破棄する必要があります。
IoCコンテナー(そのように聞こえます)は、それらのオブジェクトのインスタンス化とファクトリーインターフェースの実装を担当します。変更したコンポーネントを使用しているユーザーは、コンポーネントを取得できるようにIoCコンテナーについて知る必要があります。
上記を完了すると、選択したコンポーネントでDIから得たいと望むあらゆる利益を享受できるようになります。今こそ、それらの単体テストを追加/修正する良い機会です。既存の単体テストがあった場合は、実際のオブジェクトを注入してそれらにパッチを適用するか、モックを使用して新しい単体テストを作成するかを決定する必要があります。
アプリケーションのコンポーネントごとに上記の手順を「単純に」繰り返し、メインだけがそれについて知る必要があるまで、IoCコンテナーへの参照を上に移動します。
正しいアプローチは、使用する場合、コンストラクター注入を使用することです
私が考えているのは、いくつかの特定のクラスタイプのオブジェクトを作成するロジックを含むいくつかの特定のファクトリを作成できることです。基本的に、このクラスの静的カーネルインスタンスのNinject Get()メソッドを呼び出すメソッドを持つ静的クラス。
その後、依存関係の注入よりも、サービスロケーターが作成されます。
あなたはそれを使いたいと言いますが、理由は述べません。
DIは、インターフェースからコンクリーションを生成するためのメカニズムを提供することに他なりません。
これ自体は [〜#〜] dip [〜#〜] から来ています。コードがすでにこのスタイルで記述されていて、コンクリーションが生成される場所が1つしかない場合、DIはパーティーにそれ以上のものをもたらしません。ここにDIフレームワークコードを追加すると、コードベースが膨張して難読化されます。
あなたがdoを使用したい場合、通常は、アプリケーションの初期にファクトリー/ビルダー/コンテナー(またはその他)をセットアップして、はっきりと見えるようにします。
N.B. Ninject/StructureMapなどにコミットするのではなく、必要に応じて自分でロールするのは非常に簡単です。ただし、スタッフの離職率が妥当な場合は、認定済みのフレームワークを使用するようにグリースを塗るか、少なくともそのスタイルで記述して、学習曲線になりすぎないようにすることができます。
「正しい方法」はありませんが、いくつかの単純な原則に従う必要があります。
それで全部です。確かに、それは法律ではなく原則ですが、それらに従えば、DIを確実に実行できます(私が間違っている場合は修正してください)。
では、実行時に「新規」なしで、DIコンテナーを知らずにオブジェクトを作成する方法は?
NInjectの場合、ファクトリの作成を提供する factory extension があります。確かに、作成されたファクトリはまだカーネルへの内部参照を持っていますが、アプリケーションからアクセスすることはできません。
実際、「正しい」方法は、絶対に他に選択肢がない場合を除いて、ファクトリーをまったく使用しないことです(単体テストや特定のモックのように-プロダクションコードでは、ファクトリーを使用しません)。そうすることは実際にはアンチパターンであり、すべてのコストで避けられるべきです。 DIコンテナーの背後にある重要な点は、ガジェットがforの作業を行えるようにすることです。
上記の以前の投稿で述べたように、IoCガジェットがアプリ内のさまざまな依存オブジェクトの作成を担当する必要があります。つまり、DIガジェットにさまざまなインスタンス自体を作成および管理させます。これがDIの全体的なポイントです。オブジェクトは、依存するオブジェクトの作成方法や管理方法を知ってはなりません。それ以外の場合breaks疎結合。
既存のアプリケーションをすべてのDIに変換することは大きなステップですが、そうすることの明らかな困難は別として、バインディングの大部分を自動的に実行するDIツールを探索することも(あなたの人生を少し楽にするために)したいでしょう。 (Ninjectのようなものの中核は、インターフェース宣言をそれらのインターフェースの実装に使用したい具体的なクラスに一致させるために行う"kernel.Bind<someInterface>().To<someConcreteClass>()"
呼び出しです。DIガジェットがコンストラクターの呼び出しをインターセプトして、必要な依存オブジェクトインスタンスを提供します。一部のクラスの一般的なコンストラクター(ここに示す疑似コード)は次のようになります。
public class SomeClass
{
private ISomeClassA _ClassA;
private ISomeOtherClassB _ClassB;
public SomeClass(ISomeClassA aInstanceOfA, ISomeOtherClassB aInstanceOfB)
{
if (aInstanceOfA == null)
throw new NullArgumentException();
if (aInstanceOfB == null)
throw new NullArgumentException();
_ClassA = aInstanceOfA;
_ClassB = aInstanceOfB;
}
public void DoSomething()
{
_ClassA.PerformSomeAction();
_ClassB.PerformSomeOtherActionUsingTheInstanceOfClassA(_ClassA);
}
}
そのコードのnowhereは、SomeConcreteClassAまたはSomeOtherConcreteClassBのいずれかのインスタンスを作成/管理/解放したコードであることに注意してください。実際、どちらの具象クラスも参照されていませんでした。それで...魔法はどこで起こったのですか?
アプリの起動部分では、次のことが行われました(これも疑似コードですが、実際の(Ninject)にかなり近いです...)。
public void StartUp()
{
kernel.Bind<ISomeClassA>().To<SomeConcreteClassA>();
kernel.Bind<ISomeOtherClassB>().To<SomeOtherConcreteClassB>();
}
そこにある少しのコードは、Ninjectガジェットに、コンストラクターを探し、スキャンし、処理するように構成されたインターフェースのインスタンス(「Bind」呼び出し)を探して、具象クラスのインスタンスを作成して置き換えるように指示しますインスタンスが参照されます。
この作業の大部分を実行するNinject.Extensions.Conventions(さらに別のNuGetパッケージ)と呼ばれるNinjectを補完する素晴らしいツールがあります。自分でこれを構築するときに経験する優れた学習体験を失うことはありませんが、自分自身を始めるために、これは調査するためのツールかもしれません。
メモリが機能する場合、Unity(以前はMicrosoftから現在はオープンソースプロジェクトになっています)には、同じことを行うメソッド呼び出しが1つまたは2つあります。他のツールにも同様のヘルパーがあります。
選択するパスが何であれ、DIトレーニングの大部分については必ずMark Seemannの本を読んでください。ただし、マークのようなソフトウェアエンジニアリングの世界の「偉大な人」でさえ、重大なエラーを引き起こす可能性があることを指摘しておく必要があります。彼の本のNinjectは、Ninjectのためだけに書かれた別のリソースです。私はそれを読んでいます: 依存性注入のためのNinjectの習得