2009-12-12 33 views
8

依存性注入にかなり新しく、これが反パターンであるかどうかを判断しようとしています。依存性注入を使用した依存性注入器の注入

のは、私が3つのアセンブリがあるとしましょう:

Foo.Shared - this has all the interfaces 
Foo.Users - references Foo.Shared 
Foo.Payment - references Foo.Shared 

Foo.UsersはFoo.Payment内に構築されたオブジェクトを必要とし、Foo.PaymentもFoo.Usersからのものを必要とします。これは、ある種の循環依存を作成します。

私が使用しているDependency Injectionフレームワーク(この場合はNInject)を代理するFoo.Sharedのインターフェイスを定義しました。コンテナアプリケーションで

public interface IDependencyResolver 
{ 
    T Get<T>(); 
} 

、私はこのインタフェースの実装を持っている:

public class DependencyResolver:IDependencyResolver 
{ 
    private readonly IKernel _kernel; 

    public DependencyResolver(IKernel kernel) 
    { 
     _kernel = kernel; 
    } 

    public T Get<T>() 
    { 
     return _kernel.Get<T>(); 
    } 
} 

設定は次のようになります。

public class MyModule:StandardModule 
{ 
    public override void Load() 
    { 
     Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel); 
     Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly 
     ... 
    } 
} 

これは私がFoo.Payment.SomeTypeの新しいオブジェクトをインスタンス化することができますFooの内部から直接参照を必要としないユーザ:

public class UserAccounts:IUserAccounts 
{ 
    private ISomeType _someType; 
    public UserAccounts(IDependencyResolver dependencyResolver) 
    { 
     _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType 
    } 
} 

これは、この例ではUserAccountsクラスの正確な依存関係がどのようなものであるか不明確になります。これは良い方法ではないと思います。

どのようにこれを達成できますか?

どのような考えですか?

+1

+1舌トライスターのタイトルです。 – womp

+0

ここに同じ、私はタイトルが大好きです:) –

答えて

7

多少議論の余地はありますが、これは反パターンです。それはサービスロケータとして知られていますが、それは適切なデザインパターンと考えていますが、私はそれをアンチパターンと見なします。

この問題は、 UserAccountsクラスはの代わりにではなくの代わりにになります。コンストラクタはIDependencyResolverが必要であると述べていますが、何を入れるべきかは記述していません。 ISomeTypeを解決できないIDependencyResolverを渡すと、スローされます。

さらに悪いことに、それ以降の繰り返しで、の他のタイプをUserAccountsから解決することができます。それはうまくコンパイルするつもりですが、型を解決できない場合に実行時にスローする可能性があります。

そのルートには行かないでください。

与えられた情報から、循環依存性で特定の問題をどのように解決するべきかを正確に説明することは不可能ですが、私はあなたのデザインを考え直すことをお勧めします。多くの場合、循環参照はのリーキーアブストラクションの症状ですので、APIを少し改造すると消えてしまいます。小さな変更が必要なことはよくあります。

一般的に、問題の解決策は間接指定の別のレイヤーを追加することです。両方のライブラリのオブジェクトを密接に連携させる必要がある場合は、通常、中間ブローカーを導入することができます。

  • 多くの場合、公開/購読モデルがうまく機能します。
  • メディエータパターンは、通信がの場合は、の両方向に進む必要があります。
  • 抽象工場を導入して、必要なときに直ちに配線する必要はなく、必要なインスタンスを取得することもできます。
+0

それは私が依存関係が明確ではないと思ったので、反パターンでなければなりません。 :)抽象的なファクトリが私の最初の選択でしたが、コードが複雑になりました。実際のアプリケーションでは、1つではなく、たくさんの別々のタイプを作成する必要があります。それぞれ異なるファクトリメソッドをハードコードするか、ジェネリックを使って具体的なクラス(ハードコーディングされたもの)にインターフェイスを関連付けます。しかし、私はこのように依存性注入フレームワークの能力を失い、リフレクションを使用して手動依存性注入/タイプリゾルバコードを必要とする点でも、バインディングを構成するのは本当に面倒になるでしょう。 – andreialecu

+0

いつも支払うべき価格があります:)コンテナを構成するのが面倒になるということには同意しません。それはかなり長続きし、冗長になるかもしれませんが、ほとんど宣言的なコードがたくさんあります。優れたトレードオフ。長い風合いの大部分は、コンベンションベースの設定で解決できるかもしれません。特に、同様の抽象ファクトリがたくさんあり、すべて同じ方法で設定する必要がある場合は特にそうです。 Castle Windsorには、いくつかの簡単なステートメントで慣習で設定できる機能があります.NInjectでもこれを実行できるかどうかわかりません... –

1

これは私にとって少し奇妙なようです。依存関係を壊してリスクを回避するために、両方の参照を必要とするロジックを第3のアセンブリに分離することは可能ですか?

2

私はForeverDebuggingに同意します。これは、循環依存性を排除すると良いでしょう。

  • Foo.Payment.dll:ユーザー
  • Foo.Users.dllで、支払いにだけでなく処理するクラス:ユーザーにのみ処理するクラス、ではないとあなたはこのようなクラスを分けることができます参照してください支払い
  • Foo.UserPayment.dll:次に支払いとユーザーの両方を処理するクラス

あなたは1〜2人を参照するアセンブリが、依存関係の無い円を持っています。

アセンブリ間の循環依存関係がある場合、必ずしもクラス間の循環依存性を持つとは限りません。たとえば、あなたはこれらの依存関係を持っているとします

  • Foo.Users.UserAccountsはFoo.Payment.PaymentHistoryによって実装されFoo.Shared.IPaymentHistory、に依存しています。
  • 異なる支払いクラス、Foo.Payment.PaymentGatewayは、Foo.Shared.IUserAccountsに依存します。 IUserAccountsはFoo.Users.UserAccountsによって実装されています。

他の依存関係はないものとします。

ここでは、アプリケーションの実行時にお互いに依存するアセンブリのサークルがあります(コンパイル時には、共有DLLを経由するため、相互に依存しません)。しかし、コンパイル時または実行時に、互いに依存するクラスのサークルはありません。

この場合、IoCコンテナは通常、余分な間接レベルを追加することなく使用することができます。 MyModuleでは、各インタフェースを適切な具象型にバインドするだけです。それぞれのクラスがその依存関係をコンストラクタの引数として受け入れるようにします。最上位レベルのアプリケーションコードがクラスのインスタンスを必要とするときは、そのクラスをIoCコンテナに問い合わせます。 IoCコンテナが、クラスが依存するすべてのものを見つけることを心配してください。



あなたは、クラス間の循環依存関係で終わる行う場合、あなたはおそらく代わりに、コンストラクタ・インジェクションのクラスのいずれか、上のプロパティインジェクション(別名セッター・インジェクション)を使用する必要があります。私はNinjectを使用しませんが、プロパティの注入をサポートしています - here is the documentation

通常、IoCコンテナはコンストラクタインジェクションを使用します。コンストラクタインジェクションは依存関係を依存するクラスのコンストラクタに渡します。しかし、循環依存がある場合、これは機能しません。クラスAとクラスBが互いに依存している場合、クラスAのインスタンスをクラスBのコンストラクタに渡す必要があります。しかし、Aを作成するには、クラスBのインスタンスをそのコンストラクタに渡す必要があります。それは鶏と卵の問題です。

プロパティインジェクションでは、IoCコンテナに最初にコンストラクタを呼び出してから、そのコンストラクタを呼び出すように指示します。通常、これはロガーなどのオプションの依存関係に使用されます。しかし、それを使用して、お互いに必要な2つのクラス間の循環依存関係を解除することもできます。

これはあまりよくありません。私は、循環的な依存関係を排除するためにクラスをリファクタリングすることをお勧めします。