Что такое инжектор конструктора?


47

Я смотрел на внедрение терминов и внедрение зависимостей, просматривая статьи по шаблонам проектирования (локатор служб).

Когда я гуглил о внедрении конструктора, я получил неясные результаты, что побудило меня проверить здесь.

Что такое инжектор конструктора? Это особый тип внедрения зависимости? Канонический пример был бы очень полезен!

редактировать

Пересматривая эти вопросы после перерыва в неделю, я вижу, как я потерял ... На всякий случай, если кто-нибудь еще появится здесь, я обновлю тело вопроса, немного узнав о себе. Пожалуйста, не стесняйтесь комментировать / исправить. Внедрение в конструктор и вложение свойств - это два типа внедрения зависимостей.


5
Первый удар в Google ясно объясняет это ... 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");
    }
}

Как вы можете себе представить, это будет печатать рубленых злодеев чистыми пополам к консоли. Это прекрасно работает, но что если мы захотим вооружить нашего самурая другим оружием? Поскольку Sword создается внутри конструктора класса 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. Например, "public void Attack (IWeapon with, string target)". И чтобы избежать дублирования кода, я бы сделал существующий метод "public void Attack (string target)" неповрежденным и вызвал бы его "this.Attack (this.weapon, target)".
Луис Анджело

И затем в сочетании с фабриками, вы можете получить методы, такие как CreateSwordSamurai ()! Хороший пример.
Гертен

2
Отличный пример! Так ясно ...
Серж ван ден Овер

@ Луиз Анджело. Извините, я не уверен, что понял ваш комментарий: «На самом деле, я думаю, вам лучше будет передать 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;
    }
}

Альтернативой является использование метода установщика (или свойства) для установки ссылки 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 экземпляр устанавливается с помощью метода установки

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.