7

コンストラクターの依存関係を介して注入されたサービスを含む基本クラスがある場合: を使用せずにサブクラスのコンストラクターを宣言することは可能: base (params)ですか?

public MyBaseClass
{
   private IServiceA _serviceA;
   private IServiceB _serviceB;
   private IServiceC _serviceC;

   public MyBaseClass(null, null, null)
   public MyBaseClass(IServiceA serviceA, IServiceB serviceB, IServiceC serviceC)
   {
       _serviceA = serviceA;
       _serviceB = serviceB;
       _serviceC = serviceC;
   }
}

そして、いくつかの追加の依存関係が注入されたサブクラス:

public MySubClassA : MyBaseClass
{
   private IServiceD _serviceD;

   public MySubClassA (null, null, null, null)
   public MySubClassA (IServiceA serviceA, IServiceB serviceB, 
                       IServiceC serviceC, IServiceD serviceD)
          : base (serviceA, serviceB, serviceC)
   {
       _serviceD = serviceD;
   }
}

ここでの問題は、複数のサブクラスがあることです。現在は 10 程度しかありませんが、その数は増えるでしょう。基本クラスに別の依存関係を追加する必要があるたびに、すべてのサブクラスを調べて、そこにも手動で依存関係を追加する必要があります。この手作業は、私のデザインに何か問題があると思わせます。

MyBaseClassAサブクラスのコンストラクターで基本クラスが必要とするサービスを持たずにコンストラクターを宣言することは可能ですか? たとえば、 のコンストラクター MyBaseClassAがこのはるかに単純なコードのみを持つようにします。

   public MySubClassA (null)
   public MySubClassA (IServiceD serviceD)
   {
       _serviceD = serviceD;
   }

依存性注入がそこで発生し、サブクラスにも追加する必要がないように、基本クラスで何を変更する必要がありますか? LightInject IoC を使用しています。

4

1 に答える 1

17

この手作業は、私のデザインに何か問題があると思わせます。

多分あります。あなたが挙げた例を具体的に説明するのは難しいですが、多くの場合、このような問題は次のいずれかの理由で発生します。

  • デコレーターの代わりに基本クラスを使用して、横断的な問題 (ロギング、トランザクション処理、セキュリティ チェック、検証など) を実装します。
  • 注入可能な個別のコンポーネントにその動作を配置する代わりに、基本クラスを使用して共有動作を実装します。

基本クラスは、分野横断的な問題を実装に追加するために悪用されることがよくあります。その場合、基本クラスはすぐに神のオブジェクトになります。つまり、やりすぎで知識が多すぎるクラスです。これは、 Single Responsibility Principle (SRP)に違反しているため、頻繁に変更され、複雑になり、テストが困難になります。

この問題の解決策は、基底クラスをまとめて削除し、単一の基底クラスの代わりに複数のデコレータを使用することです。分野横断的な懸念事項ごとにデコレータを作成する必要があります。このように、各デコレータは小規模で焦点が絞られており、変更する理由は 1 つだけです (1 つの責任)。ジェネリック インターフェイスに基づく設計を使用することで、アーキテクチャ的に関連する型のセット全体をラップできるジェネリック デコレータを作成できます。たとえば、システム内のすべてのユース ケース実装をラップできる 1 つのデコレーターや、特定の戻り値の型を持つシステム内のすべてのクエリをラップする 1 つのデコレーターです。

基本クラスは、多くの場合、複数の実装で再利用される一連の無関係な機能を含めるために悪用されます。このすべてのロジックを基本クラスに配置する (基本クラスをメンテナンスの悪夢にまで成長させる) 代わりに、この機能を、実装が依存できる複数のサービスに抽出する必要があります。

このような設計に向けてリファクタリングを開始すると、多くの場合、実装が多くの依存関係を取得し始めることがわかります。これは、コンストラクターの過剰注入と呼ばれるアンチパターンです。コンストラクターの過剰注入は、多くの場合、クラスが SRP に違反している (複雑でテストが困難になっている) ことを示しています。しかし、基本クラスから依存関係にロジックを移動しても、実装のテストが難しくなることはありませんでした。実際、基本クラスのモデルにも同じ問題がありましたが、依存関係が隠されているという違いがありました。

実装のコードをよく見ると、何らかのコード パターンが繰り返されることがよくあります。複数の実装が同じ依存関係のセットを同じ方法で使用しています。これは、抽象化が欠落しているというシグナルです。このコードとその依存関係は、集約サービスに抽出できます。集約サービスは、実装に必要な依存関係の量を減らし、一般的な動作をラップします。

集約サービスの使用は、@SimonWhitehead がコメントで言及している「ラッパー」のように見えますが、集約サービスは依存関係と動作の両方を抽象化するものであることに注意してください。依存関係の「コンテナー」を作成し、それらの依存関係を公開プロパティを介して実装で公開する場合、実装が依存する依存関係の数を減らすことはなく、そのようなクラスの複雑さを下げることも、作成することもありません。その実装はテストが容易です。一方、集約サービスは、依存関係の数とクラスの複雑さを減らし、把握とテストを容易にします。

これらのルールに従う場合、ほとんどの場合、基底クラスを持つ必要はありません。

于 2013-07-01T11:39:20.240 に答える