Що таке інжектор конструктора?


47

Я розглядав терміни введення конструктора та впорскування залежностей, переглядаючи статті про схеми дизайну (Локатор обслуговування).

Коли я розмовився про впорскування конструктора, я отримав незрозумілі результати, що змусило мене зайти сюди.

Що таке інжектор конструктора? Це специфічний тип введення залежності? Канонічний приклад був би чудовою допомогою!

Редагувати

Переглянувши ці питання після проміжку в тиждень, я можу побачити, як я загубився ... На всякий випадок, якщо хтось інший з’явиться тут, я оновлю орган запитань, трохи вивчивши моє. Будь ласка, коментуйте / виправляйте. Впорскування конструктора та властивості введення властивостей - це два типи ін'єкційних залежностей.


5
Перший хіт в Google чітко пояснює це ... misko.hevery.com/2009/02/19/…
користувач281377

Відповіді:


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");
    }
}

Як ви можете собі уявити, це надрукує Нарізані злодії чисто навпіл до консолі. Це працює чудово, але що робити, якщо ми хотіли озброїти свого самурая іншою зброєю? Оскільки Меч створений всередині конструктора класу Samurai, ми повинні модифікувати реалізацію класу, щоб внести цю зміну.

Коли клас залежить від конкретної залежності, він вважається тісно пов'язаним з цим класом . У цьому прикладі клас Самурай щільно поєднаний з класом Меч. Коли заняття щільно з'єднані, їх не можна міняти, не змінюючи їх виконання. Щоб уникнути щільно з'єднаних класів, ми можемо використовувати інтерфейси для забезпечення рівня непрямості. Давайте створимо інтерфейс, щоб представити зброю в нашій грі.

interface IWeapon
{
    void Hit(string target);
}

Тоді наш клас Sword може реалізувати цей інтерфейс:

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

І ми можемо змінити наш клас самураїв:

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

Тепер наш самурай може бути озброєний різною зброєю. Але зачекайте! Меч все ще створюється всередині конструктора Самураїв. Оскільки нам ще потрібно змінити реалізацію Самураїв, щоб дати нашому воїну ще одну зброю, Самурай все ще міцно поєднаний з Мечем.

На щастя, є просте рішення. Замість того, щоб створювати Меч всередині конструктора Самураїв, ми можемо викласти його як параметр конструктора. Також відомий як інжекція конструктора.

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. На кшталт "публічна недійсна атака (IWeapon з, цільовий рядок)". І щоб уникнути дублювання коду, я зробив існуючий метод "public void Attack (string target)" неушкодженим і змусив його викликати "this.Attack (this.weapon, target)".
Луїз Анджело

А потім, поєднавшись із фабриками, ви можете отримати такі методи, як CreateSwordSamurai ()! Гарний приклад.
Geerten

2
Чудовий приклад! Так ясно ...
Серж ван ден Овер

@Luiz Angelo. Вибачте, я не впевнений, що я зрозумів ваш коментар: "Насправді, я думаю, вам краще послужити, передаючи IWeapon як параметр методу Attack. Ніби ..". Ви маєте на увазі перевантажити метод Attack у класі Самурай?
Пап

3

Я постараюся максимально зробити це дуже елементарним. Таким чином, ви можете спиратися на концепцію та створювати власні складні рішення чи ідеї.

А тепер уявімо, що у нас є церква під назвою Ювілейна, яка має філії у всьому світі. Наша мета - просто отримати товар з кожної галузі. Тут було б рішення з DI;

1) Створіть інтерфейс IJubilee:

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

2) Створіть клас JubileeDI, який би взяв інтерфейс IJubilee як конструктор і повернув елемент:

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, а саме JubileeGT, JubileeHOG, JubileeCOV, котрі ВСІ ОБОВ'ЯЗКОВІ успадкують інтерфейс IJubilee. Для задоволення від цього змусьте один із них реалізувати його метод як віртуальний:

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встановлюється об'єктом, який керує екземпляром A(на відміну від класу, Aякий Bбезпосередньо керує посиланням ).

Введення конструктора означає, що посилання на Bпередається в якості параметра конструктору Aі задається в конструкторі:

class A
{
    B b;

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

Альтернативою є використання методу setter (або властивості) для встановлення посилання на B. У цьому випадку об'єкт , який управляє екземпляр aз Aповинен спочатку викликати конструктор , Aа потім викликати метод установки , щоб встановити змінну - член , A.bперш ніж aбуде використовуватися. Метод останньої ін'єкції необхідний , коли об'єкти містять циклічні посилання один на один, наприклад , якщо екземпляр bз Bякий встановлений в екземплярі 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 безпосередньо . Що це значить? Як би ви це зробили в коді? (Вибачте моє незнання.)
TheSilverBullet

1
Приклад самураїв набагато кращий. Давайте всі перестанемо використовувати літери для змінних, будь ласка ...
stuartdotnet

@stuartdotnet: Я не заперечую про те, щоб приклад самураїв був кращим і гіршим, але навіщо зупиняти використання однобуквенних змінних? Якщо змінні не мають особливого значення, але є лише заповнювачами, однобуквні змінні є набагато більш компактними і суттєвими. Використовувати довші імена на кшталт itemAабо objectAпросто для того, щоб зробити ім’я довше, не завжди краще.
Джорджіо

1
Тоді ви пропустили мою думку - використання імен без опису важко читати, слідкувати та розуміти, і це не гарний спосіб пояснити точку
stuartdotnet

@stuartdotnet: Я не думаю, що я пропустив вашу думку: Ви стверджуєте, що описові назви завжди кращі і роблять код завжди більш зрозумілим і зрозумілим.
Джорджіо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.