web-dev-qa-db-ja.com

コンストラクター依存性注入ロジックに関する懸念

依存性注入の文献にはある種のルールがあり、コンストラクター注入を行うには、コンストラクター内のすべての引数を宣言する必要があると述べています。これは他のアプローチよりも優れています。

最初は、それで問題はありません。

しかし、私はこの「ルール」がMVCコントローラーの領域にのみ適用されることに気付きました。コントローラは特定のジョブを実行するために生成されるため、その目的から逸脱せずに破棄します。一般的に、「リクエストベースのシステム」に適用されます。ここで、データを取得して変換し、変換されたデータを出力するブロックがあるとします。このブロックには、IConditionStrategy(Equals、GreaterThanなど)など、構成が必要なその他のさまざまなパラメーターがあります。

ブロックを構成し、アプリケーションを実行します。次に、他の結果を取得したいので、そのブロックのパラメーターを変更します(これの良い例は、MATLABのSimulinkです)。ブロックは破棄されず、ワークスペースに存在します。ジョブ(データの変換)を完了し、ワークスペースで別のジョブを再実行するのを待ちます。ワークスペースのすべての可能なブロックを再作成することは非常に高価です(それだけでなく、複雑です)。しかし、数値からさまざまな戦略に変化する可能性のあるそのパラメーターの変更を開始するときは、そのインターフェイスが必要とするこのブロックへの特定の具体的な実装の背後にあるコードを提供する必要があります。この場合、「標準」のコンストラクター依存性注入はどのように適合しますか?

残念ながら、実行時にパラメーターを変更する必要があるため、コンストラクターを取り除く必要があります。しかし、繰り返しになりますが、コンストラクターを取り除くと、パラメーターが常に有効であることを保証できなくなります。一種のデフォルト実装を提供する必要がありますか? (私たちはサービスロケーターの罠に落ちます!)

そして、いつも私を悩ませているもう一つのこと。実行時にnullが表示される危険性を理解しています(そのため、コンストラクターインジェクションを用意する必要があります)が、オブジェクトを作成し、作成後にその内部戦略やパラメーターなどを変更しないように制約すると、何が得られますか。これに対する答えは、それをまったく変更する必要がなく、単に処分して別のものを作成することです。もちろん、この手法がWebアプリケーションなどの「リクエストベースのシステム」以外のドメインにどのように適合するかはわかりません。

たとえば、識別や学習が進行中であっても、実行時に伝達関数を変更するなど、内部戦略を持つニューロンを持つニューラルネットワークがあるとします。コンストラクターインジェクションを維持するためだけに、ニューロンを処分して、望ましい内部戦略で別のニューロンを作成することはできません。もちろん、さまざまな依存関係のインジェクションブックでは、メソッドインジェクションなどの他のタイプのインジェクションについて言及されていますが、文献によると、コンストラクターインジェクションは80%の機会で使用されるべきだと感じました。さまざまなアプリケーションを考えると、私の見積もりでは、80%をはるかに下回る必要があります。私は何かを誤解していますか?この件についてのご意見をいただければ幸いです。

例として、以下のスニペットを示します。オブジェクトは実行時に変更する必要がありますが、コンストラクターインジェクションはそれを制約します。

public class Transformation {
  private readonly IConditionStrategy _condition;
  private readonly ISource _input;

  public Transformation(IConditionStrategy condition, ISource input) {
    this._condition = condition;
    this._input = input;
  }

  public ISource Transform() {
    ISource source = new Source();

    foreach(var i in this._input) {
      if(this._condition.Check(i)){
        source.Add(i);
      }
    }

    return source;
  }
}

次の例では、実行時にパラメーターを変更します。目的のパラメーターで別のオブジェクトを破棄して再作成することはできないため、従来の依存関係注入の手法は使用しません。

public class Transformation{
  public IConditionStrategy Condition{get;set;}
  public ISource Input{get;set;}

  public Transformation() {
    // we could provide some default 
    // implementations just to avoid null
  }

  public ISource Transform(){
    ISource source = new Source();

    foreach(var i in this._input){
      if(this._condition.Check(i)){
        source.Add(i);
      }
    }

    return source;
  }
}
3
dkokkinos

@Markが気づいたように、すべての引数をコンストラクターでインスタンス化する必要があるというルールはありません。

さまざまなアプリケーションを考えると、私の見積もりでは、80%未満である必要があります。

パーセンテージの値に基づいて設計を決定することは正しくないと思います。現在のコンテキストの要件に適合する依存関係を注入する方法を使用できます。

特定の例では、IConditionStrategyは実行時に変更される可能性のある依存関係です。「外部」依存関係として導入して、引数としてメソッドに渡さないのはなぜですか。

public class Transformation
{
    public Transformation() { }

    public ISource Transform(IConditionStrategy condition, ISource input)
    {
        ISource source = new Source();

        foreach(var i in input){
             if (condition.Check(i))
             {
                 source.Add(i);
             }
        }

        return source;
    }
}

コンストラクタでは、変換の有効期間中に変更されないままであるデフォルトの戦略条件を注入できます

public class Transformation
{
    private readonly IConditionStrategy _defaultCondition;

    public Transformation(IConditionStrategy defaultCondition) 
    { 
        _defaultCondition = defaultCondition;
    }

    public ISource Transform(ISource input)
    {
        return Transform(_defaultCondition, input);
    }

    public ISource Transform(IConditionStrategy condition, ISource input)
    {
        ISource source = new Source();

        foreach(var i in input){
             if (condition.Check(i))
             {
                 source.Add(i);
             }
        }

        return source;
    }
}
2
Fabio

依存性注入の文献には、コンストラクターですべての引数を宣言する必要があることを示す一種のルールがあります

どこ?どの文学で?

あなたの例を考えると、_inputは、実際には実行時の入力です。次のようなクラスを作成してみませんか?

public class Transformation
{
    private readonly IConditionStrategy condition;

    public Transformation(IConditionStrategy condition)
    {
        this.condition = condition;
    }

    public ISource Transform(ISource input)
    {
        ISource source = new Source();
        foreach(var i in input)
            if(input.Check(i))
                source.Add(i);

        return source;
    }
}
3
Mark Seemann

あなたの質問と懸念は、依存性注入と不変性の混同に基づいていると思います。これらは2つの明確な懸念事項です。

  • 依存性注入は、オブジェクトが依存関係を取得する代わりに、外部からオブジェクトにプッシュされるという考え方です。これには、テストの容易さ、再利用の可能性の向上などの利点があります。

  • 不変性は、オブジェクトが作成されると修正されるという考え方です。それらの状態は変更できません。

これらを組み合わせると、定義ごとにオブジェクトを構築後に変更できないため、コンストラクター注入が発生します。不変性は、主に正確さのために、特にハードウェアの傾向(つまり、ムーア曲線の平坦化)のためにますます一般的になるマルチスレッド化の場合に好まれます。また、マルチスレッド化されたコンテキストでパフォーマンスが大幅に向上する場合もあります。これは、不変オブジェクトがスレッド間でその状態を同期する必要なく無期限にスレッドのメモリキャッシュに保持できるためです(オブジェクトが変更されないため)。オブジェクトのローカルスレッドキャッシュの状態が無効になると、スレッドは、オブジェクトの状態に再度アクセスする前に、更新されるまで待機する必要があります。これは人間の時間スケールでは高速ですが、コンピュータの時間スケールではかなり低速です。これに加えて、最近の一部のVMは、不変オブジェクトに応じてコードを最適化し、値を抽出してスタックに配置することでオブジェクトを完全に削除できます。これらの種類の最適化が現在CLRに存在するかどうかはわかりませんが、将来的に追加されることを期待しています。

したがって、パフォーマンスに関する懸念は根拠のないものではありませんが、実際には不変性のコストが思ったよりも少ないことに気付くかもしれません。彼らはさらに速いかもしれません。たとえば、ガベージコレクターは通常世代別です。詳細については説明しません。オブジェクトは、非常に長くぶらさがらなければ、基本的に収集に費用がかからない領域から始まります。

あなたが与える具体的な例に関して、あなたが本当にパフォーマンスに関心があるなら、私は Flyweight アプローチを検討します。これは、プリミティブConditionオブジェクトの永続的なセットを作成し、それらを作成することを意味します必要に応じて。これらのオブジェクトへの参照を含む新しいオブジェクトを作成するコストは、パフォーマンスに大きな影響を与える可能性は低いですが、実際に知るためにそのテストを行う必要があります。

2
JimmyJames