コンストラクター注入とは何ですか?


47

(サービスロケーター)デザインパターンに関する記事を読みながら、コンストラクター注入と依存性注入という用語を見てきました。

コンストラクターインジェクションについてGoogleで検索した結果、結果が不明確だったため、ここでチェックインする必要がありました。

コンストラクター注入とは何ですか?これは特定の種類の依存性注入ですか?規範的な例は大きな助けになるでしょう!

編集

1週間のギャップの後にこの質問を再訪すると、私がどれほど失われたかを見ることができます。コメント/修正してください。 コンストラクター注入とプロパティ注入は、依存性注入の2つのタイプです。


5
グーグルの最初のヒットは明らかにそれを説明して... misko.hevery.com/2009/02/19/...
user281377

回答:


95

私は専門家ではありませんが、私は助けることができると思います。そして、はい、それは依存性注入の特定のタイプです。

免責事項:これのほとんどすべてがNinject Wikiから「盗まれた」

簡単な例を見て、依存性注入の概念を調べてみましょう。あなたが次の大ヒットゲームを書いているとしましょう。まず、戦士を武装させるのに適した武器が必要です。

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

次に、戦士自身を表すクラスを作成しましょう。敵を攻撃するために、戦士はAttack()メソッドを必要とします。このメソッドが呼び出されると、その剣を使用して相手を攻撃する必要があります。

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

これで、サムライを作成して戦闘を行うことができます!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

ご想像のとおり、これにより、Chopped the evildoersが半分になってコンソールに表示されます。これはうまく機能しますが、サムライを別の武器で武装させたい場合はどうでしょうか?SwordはSamuraiクラスのコンストラクター内で作成されるため、この変更を行うにはクラスの実装を変更する必要があります。

クラスが具体的な依存関係に依存している場合、そのクラスに密結合していると言われます。この例では、SamuraiクラスはSwordクラスと密結合しています。クラスが密結合されている場合、実装を変更せずにクラスを交換することはできません。クラスの密結合を回避するために、インターフェイスを使用して、間接的なレベルを提供できます。ゲームの武器を表すインターフェースを作成しましょう。

interface IWeapon
{
    void Hit(string target);
}

次に、Swordクラスでこのインターフェイスを実装できます。

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

そして、Samuraiクラスを変更できます。

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

今、私たちのサムライはさまざまな武器で武装することができます。ちょっと待って!ソードはまだサムライのコンストラクター内で作成されます。戦士に別の武器を与えるために、サムライの実装を変更する必要があるため、サムライは依然としてソードと密結合しています。

幸いなことに、簡単な解決策があります。SamuraiのコンストラクターからSwordを作成するのではなく、代わりにコンストラクターのパラメーターとして公開できます。コンストラクターインジェクションとも呼ばれます。

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

ジョルジオが指摘したように、プロパティインジェクションもあります。それは次のようになります:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

お役に立てれば。


8
これ大好き!:)実際には物語のように読みます!ちょっと興味があるんだけど。同じサムライを複数の武器で(連続して)武装させたい場合、プロパティインジェクションが最適な方法でしょうか?
-TheSilverBullet

はい、できます。実際、IWeaponをAttackメソッドへのパラメーターとして渡したほうがいいと思います。「public void Attack(IWeapon with、string target)」のように。そして、コードの重複を避けるために、既存のメソッド「public void Attack(string target)」をそのままにして、「this.Attack(this.weapon、target)」を呼び出すようにします。
ルイスアンジェロ

そして、工場と組み合わせると、CreateSwordSamurai()などのメソッドを取得できます!いい例です。
Geerten

2
素晴らしい例!とても明確です...
セルジュ・ヴァン・デンOever

@ルイスアンジェロ。申し訳ありませんが、あなたのコメントを理解したかどうかはわかりません:「実際、攻撃方法のパラメーターとしてIWeaponを渡す方が良いと思います。SamuraiクラスのAttackメソッドをオーバーロードするということですか?
パップ

3

これを非常に初歩的なものにするために、できる限り努力します。そうすれば、コンセプトに基づいて、独自の複雑なソリューションやアイデアを作成できます。

今、ジュビリーと呼ばれる教会があり、世界中に支部があることを想像してください。私たちの目的は、各支店からアイテムを取得することです。DIを使用したソリューションは次のとおりです。

1)インターフェイスIJubileeを作成します。

public interface IJubilee
{
    string GetItem(string userInput);
}

2)IJubileeインターフェースをコンストラクターとして取り、アイテムを返すクラスJubileeDIを作成します。

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3)次に、Jubileeの3つのブランチ、つまりJubileeGT、JubileeHOG、JubileeCOVを作成します。これらはすべてIJubileeインターフェイスを継承する必要があります。面白くするために、そのうちの1つにそのメソッドを仮想として実装させます。

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4)それだけです!次に、DIの動作を見てみましょう。

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

コンテナクラスのインスタンスごとに、必要なクラスの新しいインスタンスを渡します。これにより、疎結合が可能になります。

これが一言で概念を説明することを願っています。


2

A各インスタンスが別のクラスのインスタンスを必要とするクラスがあると仮定しますB

class A
{
   B b;
}

依存性注入とBは、インスタンスを管理するオブジェクトによってへの参照が設定されることを意味しますAA参照をB直接管理するクラスを持つのではなく)。

コンストラクターインジェクションとは、への参照がBパラメーターとしてコンストラクターに渡され、コンストラクターにA設定されることを意味します。

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

別の方法は、セッターメソッド(またはプロパティ)を使用して、参照をに設定することBです。この場合、インスタンス管理対象aAマストは、最初のコンストラクタを呼び出すA以降メンバ変数を設定するセッターメソッドを呼び出すA.b前にa使用されています。インスタンス場合、オブジェクトは、例えば、相互に循環参照が含まれている場合、後者の注入方法が必要であるbBインスタンスに設定されるaのがAへ戻る参照を含みますa

**編集**

コメントに対処するための詳細を次に示します。

1.クラスAはBインスタンスを管理します

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. Bインスタンスはコンストラクターで設定されます

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. Bインスタンスは、setterメソッドを使用して設定されます

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}

...クラスAがBへの参照を直接管理するのとは対照的です。これは何を意味するのでしょうか?コードでどのように行いますか?(私の無知を
許してください

1
サムライの例は非常に優れています。してください...の変数の文字を使ってみましょうすべて停止
stuartdotnet

@stuartdotnet:私はサムライの例が良くも悪くも争っていないが、なぜ1文字の変数の使用をやめるのか?変数に特別な意味はないが、単なるプレースホルダーである場合、1文字の変数ははるかにコンパクトで、要点です。itemAまたはobjectA単に名前を長くするために、より長い名前を使用することは常に良いとは限りません。
ジョルジオ

1
それから、あなたは私の論点を逃しました-記述のない名前を使用することは読みにくく、理解しにくく、論点を説明する良い方法ではありません
stuartdotnet

@stuartdotnet:私はあなたの主張を逃したとは思わない:あなたは説明的な名前が常に優れていると主張し、コードを常により読みやすく、自己説明的にする。
ジョルジオ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.