web-dev-qa-db-ja.com

ろくでなし注射に代わるものはありますか? (デフォルトコンストラクターによる別名貧乏人の注入)

私は、いくつかのケースで「バスタードインジェクション」を使用するのが最も一般的です。 「適切な」依存関係注入コンストラクターがある場合:

public class ThingMaker {
    ...
    public ThingMaker(IThingSource source){
        _source = source;
    }

しかし、その後、public APIs(他の開発チームが消費するクラス)として意図しているクラスについては、最も必要と思われるデフォルトの「ろくでなし」コンストラクタを記述するよりも良いオプションを見つけることはできません。依存:

    public ThingMaker() : this(new DefaultThingSource()) {} 
    ...
}

ここでの明らかな欠点は、これによりDefaultThingSourceに静的な依存関係が作成されることです。理想的には、そのような依存関係はなく、消費者は必要なIThingSourceを常に注入します。ただし、これは使いにくいです。消費者は、ThingMakerを新しくして、モノの作成に取り掛かり、数か月後に必要に応じて何か他のものを注入したいと考えています。私の意見では、これにはいくつかのオプションが残っています。

  1. ろくでなしのコンストラクターを省略します。 ThingMakerの消費者にIThingSourceを理解させ、ThingMakerがIThingSourceと対話する方法を理解させ、具体的なクラスを見つけて記述し、コンストラクター呼び出しでインスタンスを注入するようにします。
  2. ろくでなしのコンストラクタを省略し、別のファクトリ、コンテナ、またはその他のブートストラップクラス/メソッドを提供します。どういうわけか、消費者に、独自のIThingSourceを記述する必要がないことを理解させます。 ThingMakerの消費者に工場またはブートストラップを見つけて理解させ、それを使用させる。
  3. バスタードコンストラクターを保持し、コンシューマーがオブジェクトを「更新」して実行できるようにし、DefaultThingSourceのオプションの静的依存関係に対処します。

少年、3番目は確かに魅力的です。別のより良いオプションはありますか? #1または#2は価値がないようです。

115

私の知る限り、この質問は、適切にデフォルト設定された疎結合APIを公開する方法に関するものです。この場合、適切なLocal Defaultがあります。この場合、依存関係はオプションと見なすことができます。 オプションの依存関係に対処する1つの方法は、代わりにProperty Injectionを使用することですConstructor Injection-実際、これはプロパティインジェクションの一種のポスターシナリオです。

ただし、Bastard Injectionの本当の危険性は、デフォルトがForeign Defaultの場合です。これは、デフォルトのコンストラクターが、デフォルトを実装するアセンブリ。ただし、この質問を理解すると、意図したデフォルトは同じアセンブリで発生します。この場合、特定の危険性は見当たりません。

いずれにせよ、以前の回答の1つで説明されているように、Facadeを検討することもできます。 Dependency Inject(DI) "friendly" library

ところで、ここで使用されている用語は my book のパターン言語に基づいています。

60
Mark Seemann

私のトレードオフは@BrokenGlassのスピンです:

1)唯一のコンストラクターはパラメーター化されたコンストラクターです

2)ファクトリメソッドを使用してThingMakerを作成し、そのデフォルトソースを渡します。

public class ThingMaker {
  public ThingMaker(IThingSource source){
    _source = source;
  }

  public static ThingMaker CreateDefault() {
    return new ThingMaker(new DefaultThingSource());
  }
}

明らかにこれはあなたの依存関係を排除するものではありませんが、このオブジェクトには依存関係があることを明確にします。必要に応じて(CreateThingMakerWithDefaultThingSource)ファクトリーメソッドをより明確にすることができます。 IThingSourceファクトリーメソッドをオーバーライドするよりも、合成を優先し続けるため、この方が好きです。 DefaultThingSourceが廃止され、DefaultThingSourceを使用してすべてのコードを検索し、アップグレードするようにマークする明確な方法がある場合、新しいファクトリメソッドを追加することもできます。

あなたはあなたの質問の可能性をカバーしました。ファクトリークラスは、クラス内の利便性または利便性のために別の場所にあります。他の魅力的でないオプションは反射ベースであり、依存関係をさらに隠します。

32
Steve Jackson

1つの代替方法は、依存関係を作成するThingMakerクラスに factory methodCreateThingSource()を含めることです。

テストのため、または別のタイプのIThingSourceが必要な場合は、ThingMakerのサブクラスを作成し、CreateThingSource()をオーバーライドして、必要な具象型を返す必要があります。明らかに、このアプローチは、主にテストのために依存関係を挿入できる必要がある場合にのみ価値がありますが、ほとんど/すべての他の目的のために別のIThingSourceを必要としません

8
BrokenGlass

「デフォルト」依存関係(Poor Man's Dependency Injectionとも呼ばれる)が必要な場合は、どこかで依存関係を初期化して「配線」する必要があります。

2つのコンストラクターは保持しますが、初期化専用のファクトリーがあります。

public class ThingMaker
{
    private IThingSource _source;

    public ThingMaker(IThingSource source)
    {
        _source = source;
    }

    public ThingMaker() : this(ThingFactory.Current.CreateThingSource())
    {
    }
}

ここで、ファクトリーでデフォルトのインスタンスを作成し、メソッドをオーバーライドできるようにします。

public class ThingFactory
{
    public virtual IThingSource CreateThingSource()
    {
        return new DefaultThingSource();
    }
}

更新:

2つのコンストラクタを使用する理由: 2つのコンストラクタは、クラスの使用方法を明確に示しています。パラメーターなしのコンストラクターの状態:インスタンスを作成するだけで、クラスはそのすべての責任を実行します。現在、2番目のコンストラクターは、クラスがIThingSourceに依存していることを示し、デフォルトの実装とは異なる実装を使用する方法を提供します。

なぜファクトリを使用するか: 1-規律:新しいインスタンスの作成はこのクラスの責任の一部ではないはずです。ファクトリクラスの方が適切です。 2- DRY:同じAPIで、他のクラスもIThingSourceに依存し、同じことを行うと想像してください。ファクトリメソッドがIThingSourceを返し、APIのすべてのクラスが新しいインスタンスの使用を自動的に開始したら、オーバーライドします。

この実装がAPI全体に意味を持ち、テストおよび拡張の目的でこの依存関係をオーバーライドする方法を提供している限り、ThingMakerをIThingSourceのデフォルト実装に結合しても問題はありません。

6
JCallico

#3に投票します。あなたは自分の生活を、そして他の開発者の生活をより簡単にするでしょう。

6
Fernando

このスタイルの注入に通常関連する例は、非常に単純です:「クラスBのデフォルトコンストラクターで、new A()を使用してオーバーロードされたコンストラクターを呼び出してください!」

現実には、多くの場合、依存関係の構築は非常に複雑です。たとえば、Bがデータベース接続やアプリケーション設定などの非クラス依存関係を必要とする場合はどうなりますか?次に、クラスBSystem.Configuration名前空間に結び付けて、一貫性を低下させながら複雑さと結合を増やし、すべてデフォルトのコンストラクタを省略することで簡単に外部化できる詳細をエンコードします。

このスタイルのインジェクションは、分離された設計の利点を認識しているが、それをコミットしたくないことを読者に伝えます。誰もがそのジューシーで簡単な低摩擦のデフォルトコンストラクターを見ると、その時点からプログラムがどれほど厳格になろうとも呼び出します。彼らは、そのデフォルトコンストラクターのソースコードを読むことなくプログラムの構造を理解することはできません。これは、アセンブリを配布するだけのオプションではありません。接続文字列名とアプリ設定キーの規則を文書化できますが、その時点ではコードは独立しておらず、開発者に正しい呪文を追い詰める責任を負います。

コードを最適化すると、彼らが言っていることを理解せずにコードを書くことができます。サイレンの歌です。最終的には、最初の努力で節約した時間よりも魔法を解くのに多くの時間が失われます。分離するかしないか。各パターンに足を置くと、両方の焦点が減ります。

2
Bryan Watts

この依存関係のOO不純性に不満がありますが、それが最終的にどのような問題を引き起こすかについては本当に言いません。

  • ThingMakerはIThingSourceに準拠しない方法でDefaultThingSourceを使用していますか?番号。
  • パラメータのないコンストラクタを廃止せざるを得ない時が来るでしょうか?現時点ではデフォルトの実装を提供できるため、ほとんどありません。

ここでの最大の問題は、テクニックを使用するかどうかではなく、名前の選択だと思います。

2
Amy B

それが価値があるものとして、私がJavaで見たすべての標準コードはこのようにします:

public class ThingMaker  {
    private IThingSource  iThingSource;

    public ThingMaker()  {
        iThingSource = createIThingSource();
    }
    public virtual IThingSource createIThingSource()  {
        return new DefaultThingSource();
    }
}

DefaultThingSourceオブジェクトを必要としない人は、createIThingSourceをオーバーライドできます。 (可能であれば、createIThingSourceへの呼び出しはコンストラクター以外のどこかになります。)C#は、Javaのようにオーバーライドすることを推奨しません。 Javaユーザーが独自のIThingSource実装を提供でき、おそらく提供する必要があります。(またはhowそれを提供するために明らかではありません。)#3は行く方法ですが、私はこれに言及すると思いました。

1
RalphChapin

単なるアイデア-おそらくもう少しエレガントですが、悲しいことに依存関係を取り除きません:

  • 「ろくでなしコンストラクタ」を削除する
  • 標準コンストラクタでは、ソースパラメータのデフォルトをnullにします
  • 次に、ソースがnullであるかどうかを確認し、これが当てはまる場合は、コンシューマが注入するものに関係なく「new DefaultThingSource()」を割り当てます。
0
Yahia

DefaultThingSourceを既定のコンストラクターから呼び出されるIThingSourceにマップする内部ファクトリー(ライブラリーの内部)を用意します。

これにより、パラメーターやIThingSourceの知識がなく、DefaultThingSourceに直接依存せずに、ThingMakerクラスを「更新」できます。

オプション#1をサポートします。拡張子は1つですmake DefaultThingSource a public class。上記の文言は、DefaultThingSourceがAPIのパブリックコンシューマから隠されることを意味しますが、私が理解しているように、デフォルトを公開しない理由はありません。さらに、特別な状況の外では、new DefaultThingSource()を常にThingMakerに渡すことができるという事実を簡単に文書化できます。

0
JSBձոգչ

真に公開されたAPIの場合、通常、2つの部分からなるアプローチを使用してこれを処理します。

  1. API内にヘルパーを作成し、APIコンシューマーがAPIから「デフォルト」インターフェース実装を選択したIoCコンテナーに登録できるようにします。
  2. APIコンシューマーが独自のIoCコンテナーなしでAPIを使用できるようにすることが望ましい場合は、同じ「デフォルト」実装が実装されているAPI内でオプションのコンテナーをホストします。

ここで非常に注意が必要な部分は、コンテナー#2をいつアクティブにするかを決定することです。最適な選択方法は、目的のAPIコンシューマーに大きく依存します。

0
Nicole Calinoiu