アンビエントコンテキストとコンストラクターインジェクション


9

データベースのISessionContext、ログ用のILogManager、および別のサービスとの通信に使用されるIServiceを必要とする多くのコアクラスがあります。すべてのコアクラスで使用されるこのクラスに依存性注入を使用したい。

2つの可能な実装があります。3つのクラスすべてのIAmbientContextを受け入れるか、3つのクラスすべてのクラスに注入するコアクラス。

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

最初の解決策:

public class AmbientContext
{
    private ISessionContext sessionContext;
    private ILogManager logManager;
    private IService service;

    public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
    {
        this.sessionContext = sessionContext;
        this.logManager = logManager;
        this.service = service;
    }
}


public class MyCoreClass(AmbientContext ambientContext)
{
    ...
}

2番目のソリューション(アンビエントコンテキストなし)

public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
    ...
}

この場合、Wichが最良のソリューションですか?


IService別のサービスとの通信に使用されるもの」とは何ですか?if IServiceが他のサービスへの曖昧な依存関係を表す場合、それはサービスロケータのように聞こえ、存在すべきではありません。クラスは、コンシューマーが何を行うかを明示的に記述するインターフェースに依存する必要があります。サービスへのアクセスを提供するためにサービスを必要とするクラスはありません。クラスには、クラスが必要とする特定のことを行う依存関係が必要です。
スコットHannen

回答:


4

「ベスト」はここでは主観的です。そのような決定と共通して、それは何かを達成する2つの等しく有効な方法の間のトレードオフです。

を作成しAmbientContextてそれを多くのクラスに注入すると、潜在的に各クラスに必要以上の情報が提供されることになります(たとえば、クラスFooはのみを使用できますがISessionContext、それについてILogManagerISession通知されます)。

それぞれをパラメーターを介して渡す場合は、各クラスに、知っておく必要があることだけを伝えます。ただし、パラメーターの数は急速に増加する可能性があり、非常に多くの非常に繰り返しの多いパラメーターを持つコンストラクターやメソッドが多すぎて、コンテキストクラスによって簡略化できる場合があります。

したがって、2つのバランスを取り、状況に応じて適切なものを選択する必要があります。クラスが1つとパラメーターが3つしかない場合、私は個人的には気にしませんAmbientContext。私にとって、転換点はおそらく4つのパラメーターです。しかし、それは純粋な意見です。あなたの転換点はおそらく私のものとは異なるので、あなたにとって正しいと思うものを使ってください。


4

質問の用語は、サンプルコードと実際には一致しません。これAmbient Contextは、依存関係のインターフェースを受け入れるためにすべてのクラスを汚染することなく、制御の反転の概念を維持しながら、モジュール内のクラスから依存関係をできるだけ簡単に取得するために使用されるパターンです。このような依存関係は、通常、ロギング、セキュリティ、セッション管理、トランザクション、キャッシング、監査に特化しているため、そのアプリケーションの横断的な懸念に対応できます。これは、追加するために何らかの形で迷惑なんだILoggingISecurityITimeProviderコンストラクタと私はあなたの必要性を理解していないすべてのクラスが、同時にすべての必要なほとんどの時間に。

ISessionインスタンスのライフタイムがインスタンスのライフタイムと異なる場合はどうなりますILoggerか?たぶん、ISessionインスタンスはすべてのリクエストとILoggerで一度作成されるべきです。したがって、コンテナ自体ではない1つのオブジェクトによってこれらの依存関係をすべて管理することは、このスレッドで説明されているライフタイム管理とローカリゼーションなどの問題があるため、正しい選択とは言えません。

IAmbientContext問題のは、すべてのコンストラクタを汚染しないという問題を解決していません。コンストラクタのシグネチャでまだ使用する必要がありますが、今回は1回だけです。

したがって、最も簡単な方法は、コンストラクターの注入や他の注入メカニズムを使用して、依存関係を横断することではなく、静的な呼び出しを使用することです。このパターンは、フレームワーク自体によって実装されることがよくあります。インターフェイスの実装を返す静的プロパティであるThread.CurrentPrincipalを確認しIPrincipalます。また、設定可能であるため、必要に応じて実装を変更することができます。したがって、それには連動していません。

MyCore 今のように見えます

public class MyCoreClass
{
    public void BusinessFeature(string data)
    {
        LoggerContext.Current.Log(data);

        _repository.SaveProcessedData();

        SessionContext.Current.SetData(data);
        ...etc
    }
}

このパターンと可能な実装については、この記事で Mark Seemannが詳しく説明しています。使用するIoCコンテナー自体に依存する実装がある場合があります。

あなたは避けたいAmbientContext.Current.LoggerAmbientContext.Current.Session上述したのと同じ理由のために。

ただし、この問題を解決する他のオプションがあります。コンテナにこの機能またはAOPがある場合は、デコレータ、動的インターセプトを使用します。アンビエントコンテキストは、クライアントが依存関係を隠すことにより、最後の手段となるはずです。DateTime.Nowまたはのような静的な依存関係を使用するという衝動を本当に模倣するインターフェースでConfigurationManager.AppSettingsあり、このニーズが非常に頻繁に発生する場合は、引き続きアンビエントコンテキストを使用します。しかし、結局のところ、コンストラクタの注入は、これらのユビキタスな依存関係を取得するのにそれほど悪い考えではないかもしれません。


3

私は避けますAmbientContext

まず、クラスが依存しているAmbientContext場合は、実際にそれが何をするのかわかりません。ネストされた依存関係のどれを使用するかを理解するために、その依存関係の使用を調べる必要があります。また、依存関係の1つが実際には複数のネストされた依存関係を表す可能性があるため、依存関係の数を調べて、クラスが過度に実行しているかどうかを確認することもできません。

次に、複数のコンストラクターの依存関係を回避するためにそれを使用している場合、このアプローチは、他の開発者(自分を含む)がそのアンビエントコンテキストクラスに新しいメンバーを追加することを奨励します。次に、最初の問題が悪化します。

第三に、依存関係のAmbientContextモックは、すべてのメンバーをモックするか、必要なものだけをモックするかを判断する必要があるため、より難しく、モックを設定するモックを設定する必要があります(またはダブルをテストします)。ユニットテストは、書き込み、読み取り、維持が難しくなります。

第4に、まとまりに欠け、単一責任原則に違反します。そのため、「AmbientContext」のような名前が付けられます。これは、多くの無関係なことを実行し、その内容に従って名前を付ける方法がないためです。

また、インターフェイスメンバーを必要としないクラスにインターフェイスメンバーを導入することで、インターフェイス分離原則に違反する可能性があります。


2

2番目(インターフェースラッパーなし)

中間クラスでカプセル化する必要があるさまざまなサービス間に何らかの相互作用がない限り、コードを複雑にし、「インターフェイスのインターフェイス」を導入するときの柔軟性を制限するだけです

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.