Dependency Injectionコンストラクターの狂気を避ける方法は?


300

私のコンストラクターは次のようになり始めています:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

パラメータリストは増え続けています。「コンテナ」は依存関係注入コンテナなので、なぜこれを実行できないのですか。

public MyClass(Container con)

すべてのクラスで?欠点は何ですか?これを行うと、私は栄光のスタティックを使用しているように感じます。IoCと依存性注入の狂気についてのあなたの考えを共有してください。


64
なぜコンテナを渡すのですか?IOCを誤解していると思います
Paul Creasey

33
コンストラクターがより多くのパラメーターを要求している場合、それらのクラスでやりすぎている可能性があります。
オースティンサローネン2010年

38
それは、コンストラクター注入を行う方法ではありません。オブジェクトはIoCコンテナーについてまったく認識していません。
duffymo 2010年

空のコンストラクターを作成するだけで、DIを直接呼び出して必要なものを要求できます。これでコンストラクタマッドネスは削除されますが、開発の途中でDIシステムを変更した場合に備えて、DIインターフェイスを使用していることを確認する必要があります。正直なところ、DIがコンストラクターに挿入するためにこれを実行する場合でも、この方法でこれを行う人は誰もいません。doh
Piotr Kula

回答:


409

コンテナーをサービスロケーターとして使用する場合、それは多かれ少なかれ、名誉ある静的ファクトリーであるというのは正しいことです。多くの理由で、これをアンチパターンと見なしています

コンストラクターインジェクションのすばらしい利点の1つは、単一責任の原則に対する違反が明白であることです。

その場合は、Facade Servicesリファクタリングする必要があります。要するに、現在必要とするきめの細かい依存関係の一部またはすべての間の相互作用を隠す、より粗い新しいインターフェースを作成します。


8
+1は、リファクタリングの取り組みを単一の概念に定量化するためのものです。awesome :)
Ryan Emerle

46
まじ?実際に?これらのパラメーターを別のクラスに移動するための間接参照を作成しましたが、まだそこにあります!それらに対処するためにもっと複雑です。
評判の悪い2010年

23
@irreputable:すべての依存関係を集約サービスに移動する縮退した場合、それは利点をもたらさない間接的なレベルの単なる1つであることに同意するので、私の言葉の選択は少しずれていました。ただし、重要なのは、細粒度の依存関係の一部のみを集約サービスに移動することです。これにより、新しいAggregate Serviceと、残された依存関係の両方における依存関係の順列の数が制限されます。これにより、両方の処理が簡単になります。
Mark Seemann、2011年

92
最高の発言:「コンストラクターインジェクションのすばらしい利点の1つは、単一責任の原則に対する違反が明白に明らかになることです。」
イゴールポポフ

2
@DonBoxその場合、再帰を停止するためにnullオブジェクト実装を書くことができます。必要なものではありませんが、要点は、コンストラクターインジェクションはサイクルを妨げないということです。それは、それらがそこにあることを明らかにするだけです。
Mark Seemann

66

私はあなたのクラスコンストラクターがあなたのIOCコンテナー期間への参照を持つべきではないと思います。これは、クラスとコンテナの間の不要な依存関係を表しています(IOCが回避しようとしている依存関係のタイプです!)。


+1 IoCコンテナーに依存しているため、他のすべてのクラスのコードの束を変更しないと、後でそのコンテナーを変更するのが難しくなります
Tseng

1
コンストラクターにインターフェイスパラメーターを指定せずに、IOCをどのように実装しますか?私はあなたの投稿を間違って読んでいますか?
Jハント

@J Huntコメントが理解できません。私にとって、インターフェースパラメーターとは、依存関係のインターフェースであるパラメーターを意味します。つまり、依存関係注入コンテナーが初期化される場合MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)(インターフェースパラメーター)です。これは、つまり、私はそのオブジェクトにすべきではない自分自身を注入依存性注入コンテナを言うように解釈@導出のポスト、とは無関係ですMyClass myClass = new MyClass(this)
ジョン・ドウ

25

パラメータを渡すことの難しさは問題ではありません。問題は、クラスの処理が多すぎて、さらに分解する必要があることです。

Dependency Injectionは、特にすべての依存関係を渡すことの苦痛が増しているため、クラスが大きくなりすぎることを示す早期警告として機能します。


44
私が間違っている場合は私を修正してください。ただし、ある時点で「すべてを接着」する必要があるため、そのためにいくつかの依存関係を取得する必要があります。たとえば、ビューレイヤーでは、テンプレートとそれらのデータを構築するときに、さまざまな依存関係(「サービス」など)からすべてのデータを取得し、このすべてのデータをテンプレートと画面に配置する必要があります。私のWebページに10種類の情報の「ブロック」がある場合、そのデータを提供するには10種類のクラスが必要です。それで、View / Templateクラスに10個の依存関係が必要ですか?
Andrew

4

私は、コンストラクターベースの依存性注入と、すべての依存関係を渡すのがいかに複雑かについて、同様の質問に遭遇しました。

私が過去に使用したアプローチの1つは、サービスレイヤーを使用してアプリケーションファサードパターンを使用することです。これは大まかなAPIを持っているでしょう。このサービスがリポジトリに依存している場合、プライベートプロパティのセッターインジェクションを使用します。これには、抽象ファクトリを作成し、リポジトリを作成するロジックをファクトリに移動する必要があります。

説明付きの詳細なコードはここにあります

複雑なサービスレイヤーでのIoCのベストプラクティス


3

私はこのスレッド全体を2回読みましたが、人々は質問されたものではなく、知っていることで反応していると思います。

JPの元の質問は、リゾルバーを送信してから一連のクラスを送信してオブジェクトを構築しているようですが、これらのクラス/オブジェクト自体がサービスであり、注入の準備が整っていると想定しています。そうでない場合はどうなりますか?

JP、あなたはレバレッジDIに探している場合コンテキストデータとの注入を混合の栄光を望んで、これらのパターン(またはなって「アンチパターン」)のいずれも、具体的アドレスという。実際には、そのような取り組みであなたをサポートするパッケージを使用することになります。

Container.GetSevice<MyClass>(someObject1, someObject2)

...このフォーマットはほとんどサポートされていません。実装に関連する悲惨なパフォーマンスに加えて、そのようなサポートのプログラミングの難しさは、オープンソース開発者にとって魅力のないものにしていると思います。

しかし、MyClassのファクトリを作成して登録でき、そのファクトリは、単に渡すために「サービス」にプッシュされないデータ/入力を受け取ることができるはずなので、実行する必要があります。データ。「アンチパターン」が否定的な結果である場合、データ/モデルを渡すための人工サービスタイプの存在を強制することは確かに否定的です(クラスをコンテナーにラップすることに対する感覚と同じです。同じ本能が適用されます)。

ただし、少し見苦しく見えても役立つフレームワークがあります。たとえば、Ninject:

コンストラクターで追加のパラメーターを指定してNinjectを使用してインスタンスを作成する

これは.NET向けであり、人気があり、本来の状態ほどきれいではありませんが、使用する言語には何でも選択できるはずです。


3

コンテナーの注入は、最終的に後悔するショートカットです。

過剰注入は問題ではありません。通常、他の構造上の欠陥、特に懸念事項の分離の症状です。これは1つの問題ではありませんが、多くの原因がある可能性があり、これを修正するのが非常に困難なのは、それらすべてを時々同時に処理しなければならないことです(スパゲッティのもつれを解くと考えてください)。

以下は、注意すべき点の不完全なリストです。

貧弱なドメイン設計(ルートの集計など)

懸念の分離が不十分(サービス構成、コマンド、クエリ)CQRSとイベントソーシングを参照してください。

ORマッパー(注意してください、これらはトラブルにつながる可能性があります)

モデルと他のDTOを表示する(決して再利用せず、最小限に保つようにしてください!!!!)


2

問題点:

1)パラメータリストが増加するコンストラクタ。

2)クラスが継承される場合(例:)RepositoryBase、コンストラクターの署名を変更すると、派生クラスが変更されます。

解決策1

IoC Containerコンストラクターに渡す

なぜ

  • パラメータリストの増加はこれ以上ありません
  • コンストラクターの署名が単純になります

何故なの

  • IoCコンテナーに密結合されたクラスを作成します。(これは、1。別のIoCコンテナーを使用する他のプロジェクトでそのクラスを使用する場合に問題が発生します。2。IoCコンテナーを変更することを決定した場合)
  • クラスの説明を少なくします。(クラスコンストラクターを実際に見て、機能するために何が必要かを言うことはできません。)
  • クラスは潜在的にすべてのサービスにアクセスできます。

解決策2

すべてのサービスをグループ化し、コンストラクターに渡すクラスを作成します

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

派生クラス

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

なぜ

  • クラスに新しい依存関係を追加しても、派生クラスには影響しません
  • クラスはIoCコンテナに依存しない
  • クラスは説明的です(その依存関係の面で)。慣例により、どのクラスがA依存しているかを知りたい場合、その情報はA.Dependency
  • コンストラクターの署名が単純になります

何故なの

  • 追加のクラスを作成する必要がある
  • サービスの登録が複雑になりますX.Dependency個別に登録する必要があります)
  • 概念的には合格と同じ IoC Container
  • ..

ソリューション2は未加工ですが、それに対する確固たる議論がある場合は、説明的なコメントをいただければ幸いです。


1

これは私が使うアプローチです

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

これは、注入を実行し、値を注入した後にコンストラクターを実行する方法の大まかなアプローチです。これは完全に機能するプログラムです。

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

私は現在、https://github.com/Jokine/ToolProject/tree/Coreのように機能する趣味のプロジェクトに取り組んでい ます


-8

どの依存性注入フレームワークを使用していますか?代わりにセッターベースの注入を使用してみましたか?

コンストラクタベースのインジェクションの利点は、DIフレームワークを使用しないJavaプログラマにとっては自然に見えることです。クラスを初期化するには5つのものが必要であり、コンストラクタには5つの引数があります。欠点はあなたが気づいたことです、あなたが多くの依存関係を持っているときそれは扱いにくくなります。

Springを使用すると、代わりに必要な値をセッターで渡すことができ、@ requiredアノテーションを使用してそれらが強制的に注入されるようにすることができます。欠点は、初期化コードをコンストラクターから別のメソッドに移動し、@ PostConstructでマークすることによってすべての依存関係が注入された後にSpringでそれを呼び出す必要があることです。他のフレームワークについてはわかりませんが、同じようなことをしていると思います。

どちらの方法でも機能しますが、それは好みの問題です。


21
コンストラクター注入の理由は、依存関係を明確にするためであり、Java開発者にとってより自然に見えるためではありません。
L-Four

8
遅いコメントですが、この答えは私を笑わせました:)
Frederik Prijck 14年

1
セッターベースの注射の場合は+1。私のクラスでサービスとリポジトリを定義している場合、それらは依存関係であることは明らかです。大規模なVB6のようなコンストラクターを作成したり、コンストラクターで愚かな割り当てコードを実行したりする必要はありません。必要なフィールドへの依存関係はかなり明白です。
Piotr Kula 2017

2018年、Springは、妥当なデフォルト値を持つ依存関係を除き、セッターインジェクションを使用しないことを正式に推奨しています。依存関係がクラスに必須である場合と同様に、コンストラクター注入が推奨されます。セッター対俳優DIに関する議論を
John Doe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.