Co to jest zastrzyk konstruktora?

47

Podczas przeglądania artykułów na temat wzorców projektowych (lokalizator usług) przyglądałem się terminom wstrzykiwanie konstruktora i wstrzykiwanie zależności.

Kiedy przejrzałem Google na temat wstrzykiwania konstruktora, otrzymałem niejasne wyniki, które skłoniły mnie do sprawdzenia się tutaj.

Co to jest zastrzyk konstruktora? Czy to specyficzny rodzaj wstrzyknięcia zależności? Świetnym rozwiązaniem byłby kanoniczny przykład!

Edytować

Powracając do tych pytań po upływie tygodnia, widzę, jak bardzo się zgubiłem ... Na wypadek, gdyby ktoś tu pojawił się, zaktualizuję treść pytania, zapoznając się z moim. Prosimy o komentarz / poprawienie. Zastrzyk konstruktora i zastrzyk właściwości są dwoma rodzajami wstrzyknięcia zależności.

TheSilverBullet
źródło
5
Pierwsze trafienie w google wyraźnie to wyjaśnia ... misko.hevery.com/2009/02/19/…
user281377

Odpowiedzi:

95

Nie jestem ekspertem, ale myślę, że mogę pomóc. I tak, jest to szczególny rodzaj wstrzykiwania zależności.

Oświadczenie: Prawie wszystko zostało „skradzione” z Wiki Ninject

Przeanalizujmy pomysł wstrzykiwania zależności, przechodząc przez prosty przykład. Załóżmy, że piszesz następną hitową grę, w której szlachetni wojownicy walczą o wielką chwałę. Po pierwsze, potrzebujemy broni odpowiedniej do uzbrojenia naszych wojowników.

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

Następnie stwórzmy klasę reprezentującą samych naszych wojowników. Aby zaatakować wrogów, wojownik będzie potrzebował metody Attack (). Po wywołaniu tej metody powinien użyć Miecza, aby uderzyć przeciwnika.

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

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

Teraz możemy stworzyć naszych samurajów i walczyć!

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

Jak możesz sobie wyobrazić, to wydrukuje Wytnij złoczyńców do połowy na konsoli. Działa to dobrze, ale co, jeśli chcielibyśmy uzbroić naszego Samuraja w inną broń? Ponieważ Miecz jest tworzony wewnątrz konstruktora klasy Samuraj, musimy zmodyfikować implementację klasy, aby dokonać tej zmiany.

Kiedy klasa jest zależna od konkretnej zależności, mówi się, że jest ściśle powiązana z tą klasą . W tym przykładzie klasa samurajów jest ściśle powiązana z klasą miecza. Gdy klasy są ściśle powiązane, nie można ich zamieniać bez zmiany ich implementacji. Aby uniknąć ścisłego łączenia klas, możemy użyć interfejsów, aby zapewnić poziom pośredni. Stwórzmy interfejs reprezentujący broń w naszej grze.

interface IWeapon
{
    void Hit(string target);
}

Następnie nasza klasa Sword może wdrożyć ten interfejs:

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

I możemy zmienić naszą klasę samurajów:

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

Teraz nasz samuraj może być uzbrojony w inną broń. Ale poczekaj! Miecz wciąż powstaje w konstruktorze Samuraja. Ponieważ wciąż musimy zmienić implementację Samuraja, aby dać naszemu wojownikowi kolejną broń, Samuraj jest nadal ściśle związany z Mieczem.

Na szczęście istnieje łatwe rozwiązanie. Zamiast tworzyć Miecz z konstruktora Samuraja, możemy go odsłonić jako parametr konstruktora. Znany również jako wstrzykiwanie konstruktora.

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

Jak zauważył Giorgio, istnieje również zastrzyk nieruchomości. To byłoby coś w stylu:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


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

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

}

Mam nadzieję że to pomoże.

Luiz Angelo
źródło
8
Bardzo mi się podobało! :) Właściwie to brzmi jak historia! Po prostu ciekawy. Jeśli chcesz uzbroić tego samego Samuraja w wiele broni (sekwencyjnie), zastrzyk nieruchomości byłby najlepszym sposobem, prawda?
TheSilverBullet
Tak, możesz to zrobić. Właściwie myślę, że lepiej byłoby przekazać IWeapon jako parametr do metody Attack. Jak „public void Attack (IWeapon with, string target)”. Aby uniknąć powielania kodu, zmienię istniejącą metodę na „public void Attack (string target)” na „nienaruszoną” i nadam jej nazwę „this.Attack (this. Weapon, target)”.
Luiz Angelo
A następnie w połączeniu z fabrykami możesz uzyskać metody takie jak CreateSwordSamurai ()! Niezły przykład.
Geerten,
2
Świetny przykład! Tak jasne ...
Serge van den Oever
@Liziz Angelo. Przepraszam, nie jestem pewien, czy zrozumiałem twój komentarz: „Właściwie myślę, że lepiej byłoby przekazać IWeapon jako parametr do metody Attack. Jak ...”. Masz na myśli przeciążenie metody Attack w klasie Samurai?
Pap.
3

Spróbuję jak najwięcej, aby uczynić to bardzo elementarnym. W ten sposób możesz bazować na koncepcji i tworzyć własne złożone rozwiązania lub pomysły.

Teraz wyobraź sobie, że mamy kościół o nazwie Jubileusz, który ma oddziały na całym świecie. Naszym celem jest zdobycie przedmiotu z każdej gałęzi. Oto rozwiązanie z DI;

1) Utwórz interfejs IJubilee:

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

2) Utwórz klasę JubileeDI, która pobierze interfejs IJubilee jako konstruktor i zwróci element:

public class JubileeDI
{
    readonly IJubilee jubilee;

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

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

3) Teraz utwórz trzy broszury Jubilee, a mianowicie JubileeGT, JubileeHOG, JubileeCOV, które MUSZĄ odziedziczyć interfejs IJubilee. Dla zabawy spraw, aby jeden z nich zaimplementował swoją metodę jako wirtualną:

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) To jest to! Zobaczmy teraz DI w akcji:

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

Do każdej instancji klasy kontenera przekazujemy nową instancję wymaganej klasy, która umożliwia luźne sprzężenie.

Mam nadzieję, że wyjaśnia to w skrócie.

David Grant
źródło
2

Załóżmy, że masz klasę, Az której każda instancja potrzebuje instancji innej klasy B.

class A
{
   B b;
}

Wstrzykiwanie zależności oznacza, że ​​odwołanie Bjest ustawiane przez obiekt, który zarządza wystąpieniem A(w przeciwieństwie Ado Bbezpośredniego zarządzania odwołaniem przez klasę ).

Wstrzykiwanie konstruktora oznacza, że ​​odniesienie Bjest przekazywane jako parametr do konstruktora Ai ustawiane w konstruktorze:

class A
{
    B b;

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

Alternatywą jest użycie metody ustawiającej (lub właściwości) do ustawienia odwołania B. W tym przypadku obiekt, który zarządza wystąpienie az Amusi najpierw wywołać konstruktor A, a później zadzwonić setter ustawić zmienną użytkownika A.bprzed asłuży. Ta ostatnia metoda wtrysku jest konieczne, gdy obiekty zawierają cykliczne odniesienia do siebie, na przykład, jeśli przykład bz B, który jest ustawiony w przypadku aod Azawiera odniesienie do tyłu a.

** EDYTOWAĆ**

Oto kilka dodatkowych szczegółów dotyczących komentarza.

1. Klasa A zarządza instancją B.

class A
{
    B b;

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

2. Instancja B jest ustawiona w konstruktorze

class A
{
    B b;

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

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

3. Instancja B jest ustawiana za pomocą metody 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);
}
Giorgio
źródło
... w przeciwieństwie do konieczności klasę zarządzający odniesienie do B bezpośrednio . Co to znaczy? Jak zrobiłbyś to w kodzie? (Przepraszam za moją ignorancję.)
TheSilverBullet
1
Przykład samuraja jest o wiele lepszy. Przestańmy używać liter do zmiennych, proszę ...
stuartdotnet
@stuartdotnet: Nie spieram się o to, że przykład samuraja jest lepszy od gorszego, ale po co przestać używać literowych zmiennych? Jeśli zmienne nie mają żadnego szczególnego znaczenia, ale są tylko symbolami zastępczymi, zmienne jednoliterowe są znacznie bardziej zwarte i na temat. Używanie dłuższych nazw, takich jak itemAlub objectAtylko w celu przedłużenia nazwy, nie zawsze jest lepsze.
Giorgio
1
Potem nie rozumiesz, o co mi chodzi - używanie
nieopisowych
@stuartdotnet: Nie sądzę, że nie zauważyłem twojego punktu: twierdzisz, że opisowe nazwy są zawsze lepsze i sprawiają, że kod jest zawsze bardziej czytelny i zrozumiały.
Giorgio