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からのものも必要です。これにより、ある種の循環依存関係が作成されます。

使用している依存性注入フレームワーク(この場合は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.Users内からの新しいオブジェクトをインスタンス化できます。

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、この場合のクラスの正確な依存関係が不明確になり、これは良い習慣ではないと思います。

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

何かご意見は?

4

3 に答える 3

7

多少物議を醸していますが、はい、これはアンチパターンです。これはService Locatorとして知られており、適切な設計パターンと考える人もいますが、私はアンチパターンと考えています。

この問題は、たとえば UserAccounts クラスの使用が明示的ではなく暗黙的になることです。コンストラクターは、IDependencyResolver が必要であると述べていますが、何を入れるべきかについては述べていません。ISomeType を解決できない IDependencyResolver を渡すと、スローされます。

さらに悪いことに、後の反復で、 UserAccounts 内から他のタイプを解決したくなるかもしれません。問題なくコンパイルされますが、型を解決できない場合は、実行時にスローされる可能性があります。

その道を行かないでください。

提供された情報から、循環依存関係に関する特定の問題をどのように解決すべきかを正確に伝えることは不可能ですが、設計を再考することをお勧めします。多くの場合、循環参照はLeaky Abstractionsの症状であるため、おそらく API を少し改造すればなくなるでしょう。必要な小さな変更に驚くことがよくあります。

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

  • 多くの場合、パブリッシュ/サブスクライブモデルがうまく機能します。
  • Mediatorパターンは、通信が双方向で行われる必要がある場合に代替手段を提供する可能性があります。
  • また、 Abstract Factoryを導入して、必要なインスタンスをすぐに接続するのではなく、必要なときに取得することもできます。
于 2009-12-12T08:21:16.250 に答える
2

私は ForeverDebugging に同意します - 循環依存を排除​​するのは良いことです。次のようにクラスを分離できるかどうかを確認します。

  • Foo.Payment.dll: ユーザーではなく支払いのみを処理するクラス
  • Foo.Users.dll: ユーザーのみを処理し、支払いを処理しないクラス
  • Foo.UserPayment.dll: 支払いとユーザーの両方を処理するクラス

次に、他の 2 つのアセンブリを参照する 1 つのアセンブリがありますが、依存関係の循環はありません。

アセンブリ間に循環依存関係がある場合でも、必ずしもクラス間に循環依存関係があるとは限りません。たとえば、次の依存関係があるとします。

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

他に依存関係はないと仮定します。

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

この場合、追加レベルの間接化を追加することなく、IoC コンテナーを通常どおり使用できるはずです。MyModule では、各インターフェイスを適切な具象型にバインドするだけです。各クラスがその依存関係をコンストラクターへの引数として受け入れるようにします。トップレベルのアプリケーション コードでクラスのインスタンスが必要な場合は、IoC コンテナーにクラスを要求します。クラスが依存するすべてのものを見つけることを IC コンテナーに任せます。



クラス間の依存関係が循環してしまう場合は、おそらく、コンストラクター注入ではなく、クラスの 1 つでプロパティ注入 (別名セッター注入) を使用する必要があります。私は Ninject を使用していませんが、プロパティ インジェクションをサポートしています。ドキュメントはこちらです

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

プロパティ インジェクションでは、最初にコンストラクターを呼び出し、次に構築されたオブジェクトにプロパティを設定するように IoC コンテナーに指示します。通常、これはロガーなどのオプションの依存関係に使用されます。ただし、相互に必要とする 2 つのクラス間の循環依存関係を解消するためにも使用できます。

ただし、これは見栄えが悪いため、クラスをリファクタリングして循環依存関係を排除することを強くお勧めします。

于 2009-12-12T03:25:21.133 に答える
1

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

于 2009-12-12T02:26:06.847 に答える