Proszę sprzedaj mnie na pojemnikach IoC

17

Widziałem kilka polecających użycie kontenerów IoC w kodzie. Motywacja jest prosta. Weź następujący wstrzyknięty kod zależności:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest(
        std::auto_ptr<Dependency> d = std::auto_ptr<Dependency>(new ConcreteDependency)
    ) : d_(d)
    {
    }
};


TEST(UnitUnderTest, Example)
{
    std::auto_ptr<Dependency> dep(new MockDependency);
    UnitUnderTest uut(dep);
    //Test here
}

W:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest()
    {
        d_.reset(static_cast<Dependency *>(IocContainer::Get("Dependency")));
    }
};


TEST(UnitUnderTest, Example)
{
    UnitUnderTest uut;
    //Test here
}

//Config for IOC container normally
<Dependency>ConcreteDependency</Dependency>

//Config for IOC container for testing
<Dependency>MockDependency</Dependency>

(Powyższe jest oczywiście hipotetycznym przykładem C ++)

Chociaż zgadzam się, że upraszcza to interfejs klasy poprzez usunięcie parametru konstruktora zależności, myślę, że lekarstwo jest gorsze od choroby z kilku powodów. Po pierwsze, i to jest dla mnie duży, to powoduje, że twój program zależy od zewnętrznego pliku konfiguracyjnego. Jeśli potrzebujesz pojedynczego wdrożenia binarnego, po prostu nie możesz używać tego rodzaju kontenerów. Drugi problem polega na tym, że interfejs API jest teraz słabo i gorzej, ściśle napisany. Dowodem (w tym hipotetycznym przykładzie) jest argument ciągu do kontenera IoC i rzut na wynik.

Więc ... czy są inne korzyści z używania tego rodzaju pojemników, czy też nie zgadzam się z tymi, którzy zalecają pojemniki?

Billy ONeal
źródło
1
Przykładowy kod wygląda na naprawdę zły przykład implementacji IoC. Znam tylko C #, ale z pewnością istnieje sposób na wprowadzenie konstruktora i konfigurację programową również w C ++?
rmac

Odpowiedzi:

12

W dużej aplikacji z wieloma warstwami i dużą ilością ruchomych części, wady zaczynają wyglądać na niewielkie w porównaniu z zaletami.

Kontener „upraszcza interfejs” klasy, ale robi to w bardzo ważny sposób. Kontener jest rozwiązaniem problemu, który powoduje wstrzykiwanie zależności, którym jest potrzeba przekazywania zależności w dowolnym miejscu, w dół przez wykresy obiektów i obszary funkcjonalne. Masz tutaj jeden mały przykład, który ma jedną zależność - co, jeśli ten obiekt miał trzy zależności, a obiekty, które od niego zależały, miały wiele obiektów, które zależały od nich , i tak dalej? Bez kontenera obiekty znajdujące się u góry tych łańcuchów zależności stają się odpowiedzialne za śledzenie wszystkich zależności w całej aplikacji.

Istnieją również różne rodzaje pojemników. Nie wszystkie z nich mają ciąg znaków i nie wszystkie wymagają plików konfiguracyjnych.

nlawalker
źródło
3
Ale duży projekt pogarsza problemy, o których już wspomniałem . Jeśli projekt jest duży, plik konfiguracyjny sam staje się ogromnym magnesem zależności, a jeszcze jedna literówka prowadzi do nieokreślonego zachowania w czasie wykonywania. (tzn. literówka w pliku konfiguracyjnym spowoduje, że rzutowanie spowoduje niezdefiniowane zachowanie, jeśli zwrócony zostanie niewłaściwy typ). Jeśli chodzi o zależności zależności, tutaj nie mają one znaczenia. Albo konstruktor zajmie się ich wykonaniem, albo test będzie kpił z zależności najwyższego poziomu.
Billy ONeal
... ciąg dalszy ... Gdy program nie jest testowany, na każdym kroku tworzona jest domyślna konkretna zależność. Wrt inne rodzaje pojemników, masz jakieś przykłady?
Billy ONeal
4
TBH Nie mam dużego doświadczenia z implementacjami DI, ale spójrz na MEF na .NET, który może być, ale nie musi być napisany w ciągach, nie ma pliku konfiguracyjnego, a konkretne implementacje interfejsów są „rejestrowane” na samych zajęciach. BTW, kiedy mówisz: „konstruktor dba o ich wykonanie” - jeśli to właśnie robisz (inaczej „zastrzyk konstruktora biedaka”), to nie potrzebujesz pojemnika. Zaletą kontenera jest to, że kontener centralizuje informacje o wszystkich tych zależnościach.
nlawalker
+1 za ten ostatni komentarz - ostatnie zdanie jest odpowiedzią :)
Billy ONeal
1
Świetna odpowiedź, która pomogła mi zrozumieć cały punkt. Chodzi nie tylko o uproszczenie schematu, ale o to, aby elementy na górze łańcucha zależności były tak samo naiwne jak elementy na dole.
Christopher Berman
4

Struktura Guice IoC z Java nie polega na pliku konfiguracyjnym, ale na kodzie konfiguracyjnym . Oznacza to, że konfiguracja nie różni się kodem od kodu tworzącego twoją aplikację i może być refaktoryzowana itp.

Uważam, że Guice to platforma Java IoC, która w końcu poprawnie skonfigurowała.


źródło
Czy są jakieś podobne przykłady dla C ++?
Billy ONeal
@Billy, nie znam wystarczająco C ++, aby móc powiedzieć.
0

Ta wspaniała odpowiedź Bena autorstwa Scheirmana zawiera szczegółowe przykłady kodu w języku C #; niektóre zalety kontenerów IoC (DI) to:

  • Twój łańcuch zależności może zostać zagnieżdżony i szybko staje się niewygodne, aby połączyć je ręcznie.
  • Umożliwia korzystanie z programowania zorientowanego na aspekt .
  • Deklaratywne i zagnieżdżone transakcje w bazie danych.
  • Deklaratywna i zagnieżdżona jednostka pracy.
  • Logowanie.
  • Warunki przed / po (projekt na podstawie umowy).
dodgy_coder
źródło
1
Nie rozumiem, jak żadnej z tych rzeczy nie można zaimplementować za pomocą prostego wstrzyknięcia konstruktora.
Billy ONeal
Przy dobrym projekcie wystarczy zastrzyk konstruktora lub inna metoda; zalety tych kontenerów IoC mogą być tylko w szczególnych przypadkach. Przykład podany z INotifyPropertyChanged w projekcie WPF był jednym z takich przykładów.
dodgy_coder