この説明では、オブジェクト コンストラクターが受け取るパラメーターには、状態依存性とサービス依存性の 2 種類があります。IOC コンテナーを使用してサービスの依存関係を提供するのは簡単です。DI が引き継ぎます。しかし対照的に、状態の依存関係は通常、クライアントだけが知っています。つまり、オブジェクト リクエスタです。
クライアントが IOC コンテナーを介して状態パラメーターを提供することは、非常に苦痛であることが判明しました。これを行うためのいくつかの異なる方法を示しますが、そのすべてに大きな問題があり、私が見逃している別のオプションがないかコミュニティに尋ねます。さぁ、始めよう:
プロジェクト コードに IOC コンテナーを追加する前に、次のようなクラスから始めました。
class Foobar {
//parameters are state dependencies, not service dependencies
public Foobar(string alpha, int omega){...};
//...other stuff
}
Logger サービスの依存関係を Foobar クラスに追加することにしました。これは、おそらく DI を通じて提供します。
class Foobar {
public Foobar(string alpha, int omega, ILogger log){...};
//...other stuff
}
しかし、クラス Foobar 自体を「スワップ可能」にする必要があるとも言われました。つまり、Foobar インスタンスをサービス検索する必要があります。ミックスに新しいインターフェイスを追加します。
class Foobar : IFoobar {
public Foobar(string alpha, int omega, ILogger log){...};
//...other stuff
}
サービス ロケーターを呼び出すと、ILogger サービスの依存関係が DI されます。残念ながら、状態の依存関係である Alpha と Omega については、同じことが当てはまりません。一部のコンテナーでは、これに対処するための構文が提供されています。
//Unity 2.0 pseudo-ish code:
myContainer.Resolve<IFoobar>(
new parameterOverride[] { {"alpha", "one"}, {"omega",2} }
);
この機能は気に入っていますが、型が指定されておらず、(インテリセンスなどを介して) どのパラメーターを渡さなければならないかが開発者に明らかでない点が気に入りません。だから私は別の解決策を見ます:
//This is a "boiler plate" heavy approach!
class Foobar : IFoobar {
public Foobar (string alpha, int omega){...};
//...stuff
}
class FoobarFactory : IFoobarFactory {
public IFoobar IFoobarFactory.Create(string alpha, int omega){
return new Foobar(alpha, omega);
}
}
//fetch it...
myContainer.Resolve<IFoobarFactory>().Create("one", 2);
上記は型安全性とインテリセンスの問題を解決しますが、(1) クラス Foobar に DI ではなくサービスロケーターを介して ILogger をフェッチするように強制し、(2) 一連のボイラープレート (XXXFactory、IXXXFactory) を作成する必要があります。私が使用する可能性のあるすべての種類の Foobar 実装について。純粋なサービス ロケーター アプローチを使用することに決めたとしても、問題にはならないかもしれません。しかし、これを機能させるために必要なボイラープレートのすべてにまだ耐えられません。
そこで、コンテナーが提供するサポートの別のバリエーションを試します。
//overall, this is a pseudo-memento pattern.
class Foobar : IFoobar {
public Foobar (FoobarParams parms){
this.alpha = parms.alpha;
this.omega = parms.omega;
};
//...stuff
}
class FoobarParams{
public FoobarParams(string alpha, int omega){...};
}
//fetch an instance:
FoobarParams parms = new FoobarParams("one",2);
//Unity 2.0 pseudo-code...
myContainer.resolve<IFoobar>(new parameterOverride(parms) );
このアプローチにより、私は自分のインテリセンスを半分回復しました。しかし、「FoobarParams」パラメーターの指定を忘れた可能性があるエラーを検出するには、実行時まで待たなければなりません。
それでは、新しいアプローチを試してみましょう。
//code named "concrete creator"
class Foobar : IFoobar {
public Foobar(string alpha, int omega, ILogger log){...};
static IFoobar Create(string alpha, int omega){
//unity 2.0 pseudo-ish code. Assume a common
//service locator, or singleton holds the container...
return Container.Resolve<IFoobar>(
new parameterOverride[] {{"alpha", alpha},{"omega", omega} }
);
}
//Get my instance:
Foobar.Create("alpha",2);
具体的な「Foobar」クラスを使用して IFoobar を作成していることは、実際には気にしません。これは、私のコードで変更する予定のない基本概念を表しています。カプセル化されているため、静的な「Create」に型安全性がないことも気にしません。私のインテリセンスも機能しています!この方法で作成された具体的なインスタンスは、指定された状態パラメーターが適用されない場合は無視されます (Unity 2.0 の動作)。おそらく、別の具象実装「FooFoobar」には正式な引数名の不一致がある可能性がありますが、それでもかなり満足しています。
しかし、このアプローチの大きな問題は、Unity 2.0 でのみ効果的に機能することです (構造マップのパラメーターが一致しないと例外がスローされます)。だから、Unity と一緒にいるだけでいいのです。問題は、Structure Map がますます好きになり始めていることです。だから今、私はさらに別のオプションに行きます:
class Foobar : IFoobar, IFoobarInit {
public Foobar(ILogger log){...};
public IFoobar IFoobarInit.Initialize(string alpha, int omega){
this.alpha = alpha;
this.omega = omega;
return this;
}
}
//now create it...
IFoobar foo = myContainer.resolve<IFoobarInit>().Initialize("one", 2)
これで、他のアプローチとの妥協点がいくらか得られました: (1) 私の引数はタイプ セーフ/インテリセンス対応です (2) ILogger を DI (上に表示) またはサービス ロケーター ( 3) 1 つまたは複数の別個の具体的な FoobarFactory クラスを作成する必要はありません (前述の冗長な「ボイラープレート」の例のコードとは対照的です)。(4) 「インターフェースを正しく使いやすく、使いにくくする」という原則を合理的に支持します。使い方を間違えます。」少なくとも、これまでに説明した代替案よりも悪くないことはほぼ間違いありません。
受け入れの壁はまだ1つ残っています。「デザイン・バイ・コントラクト」も適用したいのです。
私が提示したすべてのサンプルは、最も一般的に実践されている「不変」サポートを維持したいため、意図的にコンストラクター注入 (状態の依存関係) を優先していました。つまり、コンストラクターが完了すると、不変条件が確立されます。
上記のサンプルでは、オブジェクトの構築が完了すると、不変条件は確立されません。私が自家製の「契約による設計」を行っている限り、 Initialize(...) メソッドが呼び出されるまで不変式をテストしないように開発者に伝えることができます。
しかし、もっと要点を言えば、.net 4.0 が出てきたら、その「コード コントラクト」サポートをコントラクトによる設計に使用したいと考えています。私が読んだことから、この最後のアプローチとは互換性がありません。
呪い!
もちろん、私の哲学全体がずれていることにも気付きます。おそらく、サービスロケーターを介して Foobar : IFoobar を呼び出すことは、それがサービスであることを意味し、サービスには他のサービスの依存関係のみがあり、状態の依存関係はありません (これらの例のアルファやオメガなど)。私はそのような哲学的な問題にも耳を傾けますが、その思考の道に私を導く、どの半権威的な参考文献を読むべきかを知りたい.
だから今、私はそれをコミュニティに向けます。まだ検討していないアプローチは何ですか?自分の選択肢を使い果たしたと本当に信じなければなりませんか?
ps この種の問題は、他の問題とともに、少なくとも .net における IOC コンテナーのアイデア全体が時期尚早であると私に信じさせます。「C」言語をオブジェクト指向に感じさせるために頭を抱えていた時代を思い出します (変なマクロを追加するなど)。私たちが探しているのは、CLR と IOC コンテナーの言語サポートです。たとえば、「開始」と呼ばれる新しい種類のインターフェースを想像してみてください。初期化はインターフェイスのように機能しますが、特定のコンストラクター シグネチャも必要です。残りは生徒の練習として残します...