Dlaczego potrzebuję kontener IoC w przeciwieństwie do prostego kodu DI? [Zamknięte]


598

Używam Dependency Injection (DI) od dłuższego czasu, wstrzykując albo do konstruktora, właściwości lub metody. Nigdy nie czułem potrzeby używania kontenera Inversion of Control (IoC). Im więcej czytam, tym bardziej odczuwam presję ze strony społeczności na korzystanie z kontenera IoC.

Grałem z kontenerami .NET, takimi jak StructureMap , NInject , Unity i Funq . Nadal nie widzę, w jaki sposób kontener IoC przyniesie korzyści / ulepszy mój kod.

Obawiam się również, że zacznę używać kontenera w pracy, ponieważ wielu moich współpracowników zobaczy kod, którego nie rozumieją. Wielu z nich niechętnie uczy się nowych technologii.

Przekonaj mnie, że muszę użyć kontenera IoC. Wykorzystam te argumenty, rozmawiając z innymi programistami w pracy.


6
Dobre pytanie. Odpowiadam na to w komentarzach, ponieważ jestem nowy w idei MKOl. Wygląda to na ideę komponentów plug and play i luźnego połączenia. To, czy będziesz musiał użyć innego komponentu zamiast obecnego, zależy od potrzeby. Wykorzystanie MKOl polega na przygotowaniu kodu na taką zmianę, jeśli nastąpi IMO.
shahkalpesh

3
@shahkalpesh, ale mogę uzyskać luźne połączenie za pomocą prostej DI. Widzę jednak twój punkt widzenia w przypadku korzystania z pliku konfiguracyjnego. Nie jestem jednak pewien, czy chcę użyć pliku konfiguracyjnego. Pliki konfiguracyjne są bardzo szczegółowe, mają trudności z refaktoryzacją i przełączaniem między kilkoma plikami.
Vadim

110
po prostu dodając komentarz, ponieważ wygląda na to, że szukasz powodów, aby korzystać głównie z IoC; ale trzeba coś jeszcze powiedzieć o swoim problemie. Nie możesz napisać swojego kodu na poziomie gorszej osoby w zespole lub z obawy, że nie będzie chciała się uczyć. Twoim obowiązkiem jest nie tylko bycie profesjonalistą, ale także rozwój Twojej przyszłości, a Twój zespół nie powinien Cię powstrzymywać.
Kevin Sheffield

4
@Kevin, Właściwie używamy IoC. Nauczenie wszystkich o IoC nie było tak trudne, jak się wydawało.
Vadim,

21
Nie mam pojęcia, dlaczego moderatorzy zamykają masowo uprzywilejowane i przydatne pytania. Być może w tym przypadku Will nie rozumie pytania lub nie rozumie, do czego ludzie używają wymiany stosów? Jeśli „nie pasuje do formatu pytań i odpowiedzi stosu wymiany”, być może weź pod uwagę, że twoja polityka jest niewłaściwa i nie wszystkie z tych setek przydatnych pytań z setkami głosów oddanych i głosowanych.
NickG

Odpowiedzi:


441

Wow, nie mogę uwierzyć, że Joel sprzyjałby temu:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

Nad tym:

var svc = IoC.Resolve<IShippingService>();

Wielu ludzi nie zdaje sobie sprawy, że łańcuch zależności może zostać zagnieżdżony i szybko staje się niewygodne, aby połączyć je ręcznie. Nawet w fabrykach powielanie kodu po prostu nie jest tego warte.

Tak, pojemniki IoC mogą być złożone. Ale w tym prostym przypadku pokazałem, że jest niezwykle łatwy.


OK, uzasadnijmy to jeszcze bardziej. Załóżmy, że masz pewne encje lub obiekty modelu, które chcesz powiązać z inteligentnym interfejsem użytkownika. Ten inteligentny interfejs użytkownika (nazwiemy go Shindows Morms) chce, abyś wdrożył INotifyPropertyChanged, aby mógł odpowiednio śledzić zmiany i odpowiednio aktualizować interfejs użytkownika.

„OK, to nie brzmi tak ciężko”, więc zaczynasz pisać.

Zaczynasz od tego:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

.. i skończyć z tym :

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

To obrzydliwy kod hydrauliczny i utrzymuję, że jeśli piszesz taki kod ręcznie, to kradniesz od swojego klienta . Istnieje lepszy, mądrzejszy sposób pracy.

Słyszałeś kiedyś ten termin, pracujesz mądrzej, a nie ciężej?

Wyobraź sobie, że pojawił się jakiś inteligentny facet z twojego zespołu i powiedział: „Oto łatwiejszy sposób”

Jeśli sprawisz, że twoje nieruchomości będą wirtualne (uspokój się, to nie jest wielka sprawa), możemy automatycznie wplatać się w takie zachowanie nieruchomości. (Nazywa się to AOP, ale nie martw się o nazwę, skup się na tym, co ona dla Ciebie zrobi)

W zależności od używanego narzędzia IoC możesz zrobić coś, co wygląda następująco:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

Puf! Cała ta instrukcja INotifyPropertyChanged BS jest teraz automatycznie generowana dla Ciebie na każdym wirtualnym ustawniku właściwości danego obiektu.

Czy to magia? TAK ! Jeśli możesz zaufać faktowi, że ten kod spełnia swoje zadanie, możesz bezpiecznie pominąć całą tę właściwość zawijania mumbo-jumbo. Masz problemy biznesowe do rozwiązania.

Niektóre inne ciekawe zastosowania narzędzia IoC do wykonania AOP:

  • Deklaratywne i zagnieżdżone transakcje w bazie danych
  • Deklaratywna i zagnieżdżona jednostka pracy
  • Logowanie
  • Warunki przed / po (projekt na podstawie umowy)

28
Ups, zapomniałeś uczynić wszystkie właściwości wirtualnymi i to nie działało. Czy czas potrzebny na debugowanie również to kradzież od klienta? (Przepraszamy, jeśli nie używasz DynamicProxy.: P)
Thom

17
Poświęcasz DUŻO przejrzystości, używając opakowania INotifyPropertyChanged. Jeśli napisanie tej hydrauliki zajmuje więcej niż kilka minut, oznacza to, że masz zły interes. Mniej znaczy więcej, jeśli chodzi o gadatliwość twojego kodu!
zrozumiał

39
@ overstood - całkowicie się nie zgadzam. Po pierwsze, gdzie jasność ma znaczenie w tym przykładzie? Najbliższy konsument. Konsument wyraźnie prosi o INotifyPropertyChanged. Tylko dlatego, że można utworzyć ten typ kodu z generowania kodu mikro lub makro, nie znaczy, że to dobry pomysł, aby ją napisać. Ten gnom INotifyPropertyChanged jest po prostu rutynowy, przyziemny, hydrauliczny i faktycznie szkodzi czytelności danej klasy.
Ben Scheirman

31
Oznaczono, ponieważ mylisz wzorzec wstrzykiwania zależności i wzorzec lokalizatora usług. Ten pierwszy ma być używany zamiast drugiego. Np. Obiekt (a nawet cały system) nigdy nie powinien wywoływać funkcji Resolve (...), aby uzyskać instancję IShippingService. Obiekty, które potrzebują usługi IShippingService, powinny otrzymać odniesienie do instancji, z której powinny skorzystać podczas ich budowy, a pewna wyższa warstwa jest odpowiedzialna za połączenie dwóch obiektów ze sobą.
Nat

30
Cokolwiek to jest (IoC, DynamicProxy, AOP lub czarna magia ), czy ktoś może połączyć mnie z konkretnym artykułem / konkretną dokumentacją w ramach, która zawiera więcej szczegółów na temat osiągnięcia tego celu?
fostandy

138

Jestem z tobą, Vadim. Kontenery IoC mają prostą, elegancką i przydatną koncepcję i sprawiają, że musisz się uczyć przez dwa dni z 200-stronicowym podręcznikiem.

Osobiście jestem zakłopotany tym, jak społeczność IoC wzięła piękny, elegancki artykuł Martina Fowlera i przekształciła go w kilka złożonych ram, zwykle z 200-300 stronicowymi instrukcjami.

Staram się nie osądzać (HAHA!), Ale myślę, że ludzie, którzy używają kontenerów IoC, są (A) bardzo inteligentni i (B) pozbawieni empatii wobec ludzi, którzy nie są tak mądrzy jak oni. Wszystko ma dla nich doskonały sens, więc mają problem ze zrozumieniem, że wielu zwykłych programistów uzna, że ​​pojęcia są mylące. To przekleństwo wiedzy . Ludzie, którzy rozumieją kontenery IoC, nie mogą uwierzyć, że są ludzie, którzy ich nie rozumieją.

Najcenniejszą zaletą korzystania z kontenera IoC jest to, że możesz mieć przełącznik konfiguracji w jednym miejscu, który pozwala przełączać między, powiedzmy, trybem testowym a trybem produkcyjnym. Załóżmy na przykład, że masz dwie wersje klas dostępu do bazy danych ... jedną wersję, która logowała się agresywnie i przeprowadziła wiele sprawdzeń poprawności, z której korzystałeś podczas programowania, oraz inną wersję bez logowania lub sprawdzania, która była bardzo szybka w produkcji. Miło jest móc przełączać się między nimi w jednym miejscu. Z drugiej strony jest to dość trywialny problem, który można łatwo rozwiązać w prostszy sposób, bez złożoności kontenerów IoC.

Uważam, że jeśli używasz kontenerów IoC, twój kod staje się, szczerze mówiąc, znacznie trudniejszy do odczytania. Liczba miejsc, na które musisz spojrzeć, aby dowiedzieć się, co próbuje zrobić kod, wzrasta o co najmniej jedno. I gdzieś w niebie anioł krzyczy.


81
Myślę, że masz trochę pomieszane fakty, Joel. Najpierw pojawiła się IoC i kontenery, a potem traktat Martina, a nie odwrotnie.
Glenn Block

55
Większość pojemników pozwala szybko rozpocząć pracę. Dzięki autofacowi, mapie struktury, jedności, ninject, itp. Powinieneś mieć możliwość pracy kontenera w około 5 minut. Tak, mają zaawansowane funkcje, ale nie potrzebujesz ich do startu.
Glenn Block,

58
Joel Myślałem tak samo jak ty. Potem dosłownie i poważnie spędziłem pięć minut, aby dowiedzieć się, jak uruchomić najbardziej podstawową konfigurację i byłem zaskoczony zasadą 80/20 w akcji. Dzięki temu kod jest znacznie czystszy, szczególnie w prostych przypadkach.
Justin Bozonier

55
Programuję od mniej niż dwóch lat i przeczytałem wiki Ninject i dostałem to. Od tego czasu używam DI w wielu miejscach. Widziałeś to? Niecałe dwa lata i przeczytanie kilku stron na wiki. Nie jestem czarodziejem ani nic takiego. Nie uważam się za geniusza, ale łatwo mi to zrozumieć. Nie wyobrażam sobie, żeby ktoś, kto naprawdę rozumie OO, nie byłby w stanie go zrozumieć. Poza tym, do jakich instrukcji na 200–300 stron chodzi? Nigdy ich nie widziałem. Wszystkie używane przeze mnie pojemniki IoC są dość krótkie i do rzeczy.
JR Garcia

35
+1 dobrze napisane i przemyślane. Jedną rzeczą, o której myślę, że wiele osób nie zdaje sobie sprawy, że dodanie frameworku w celu „magicznej” obsługi czegoś automatycznie dodaje złożoności i ograniczeń systemowi. Czasami warto, ale często coś zostaje przeoczone i entropia jest uwalniana do kodu, próbując naprawić problem wymuszony przez ograniczenia. Może to zaoszczędzić czas z góry, ale ludzie muszą zdawać sobie sprawę, że może to kosztować 100 złożenie linii, próbując naprawić jakiś niejasny problem, tylko garstka ludzi naprawdę ma wystarczającą wiedzę do rozwiązania.
kemiller2002

37

Prawdopodobnie nikt nie zmusza cię do korzystania z frameworku kontenera DI. Już używasz DI do oddzielania swoich klas i poprawiania testowalności, więc zyskujesz wiele korzyści. Krótko mówiąc, preferujesz prostotę, która ogólnie jest dobrą rzeczą.

Jeśli twój system osiąga poziom złożoności, w którym ręczne DI staje się obowiązkiem (to znaczy zwiększa konserwację), zważ to z krzywą uczenia się zespołu szkieletu kontenera DI.

Jeśli potrzebujesz większej kontroli nad zarządzaniem czasem istnienia zależności (to znaczy, jeśli czujesz potrzebę wdrożenia wzorca Singleton), spójrz na pojemniki DI.

Jeśli używasz kontenera DI, używaj tylko potrzebnych funkcji. Pomiń plik konfiguracyjny XML i skonfiguruj go w kodzie, jeśli to wystarczy. Trzymaj się iniekcji konstruktora. Podstawy Unity lub StructureMap można skondensować do kilku stron.

Świetny post na blogu autorstwa Marka Seemanna na ten temat: Kiedy używać kontenera DI


Bardzo dobra odpowiedź. Jedyną rzeczą, na którą różniłbym się w opinii, jest to, czy ręczny zastrzyk EVER można uznać za mniej złożony, czy nie obowiązek.
wekempf

+1. Jedna z najlepszych odpowiedzi tutaj. Dziękujemy również za link do artykułu na blogu.
stakx - nie przyczynia się już

1
+1 za zrównoważony punkt widzenia. Kontenery IoC nie zawsze są dobre i nie zawsze są złe. Jeśli nie widzisz potrzeby, nie używaj jej. Po prostu wiedz, że narzędzie jest dostępne, jeśli widzisz potrzebę.
Phil

32

Moim zdaniem największą zaletą IoC jest możliwość scentralizowania konfiguracji twoich zależności.

Jeśli obecnie używasz wstrzykiwania zależności, kod może wyglądać tak

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Jeśli użyłeś statycznej klasy IoC, w przeciwieństwie do IMHO, bardziej skomplikowane pliki konfiguracyjne, możesz mieć coś takiego:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Wtedy twoja statyczna klasa IoC wyglądałaby tak, używam tutaj Unity.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}

11
Ben, to, o czym mówisz, to tak naprawdę lokalizacja usługi, a nie wstrzykiwanie zależności - jest różnica. Zawsze wolę pierwszy od drugiego. stevenharman.net/blog/archive/2009/09/25/…
stevenharman

23
Zamiast wykonywać IoC.Resolve <T> w domyślnym konstruktorze, powinieneś wykorzystać zdolność kontenera MKOl do zbudowania obiektu za Ciebie. Pozbądź się tego pustego konstruktora i pozwól kontenerowi MKOl zbudować obiekt za ciebie. var presenter = IoC.Resolve <CustomerPresenter> (); voila! wszystkie zależności są już podłączone.
Ben Scheirman

12
Wadą centralizacji tego rodzaju konfiguracji jest dekontekstualizacja wiedzy. Joel wskazuje na ten problem, mówiąc: „kod staje się, szczerze mówiąc, znacznie trudniejszy do odczytania”.
Scott Bellware,

6
@sbellware, jaka wiedza? Interfejs przyjęty jako podstawowa zależność służy jako umowa i powinien wystarczyć do przekazania zarówno jego celu, jak i zachowania, prawda? Podejrzewam, że możesz odnosić się do wiedzy zdobytej podczas spelunkowania przy wdrażaniu umowy. W takim przypadku musisz wyśledzić odpowiednią konfigurację? W tym celu polegam na komputerze (R # w VS, IntelliJ itp.), Ponieważ świetnie nadają się do poruszania się po węzłach i krawędziach wykresu; Zastrzegam sobie stos mojego mózgu na kontekst strony, na której aktualnie się koncentruję.
stevenharman

7
Steve, znam tekst .NET i chodziłem po nim przez wiele lat. Mam również dogłębną znajomość innych trybów i form i doskonale rozumiem, że istnieją prawdziwe kompromisy. Wiem, jak i kiedy dokonać takich kompromisów, ale różnorodność w moim życiu zawodowym przynajmniej pozwala mi konkretnie zrozumieć perspektywę Joela. Zamiast pouczać mnie o narzędziach i sztuczkach, których używam dłużej niż większość monokulturystów .NET, powiedz mi coś, czego nie wiem. Rezerwę mojego mózgu rezerwuję na różnorodne, krytyczne myślenie, zamiast zastoju i ortodoksji alt.net.
Scott Bellware,

32

Kontenery IoC są również dobre do ładowania głęboko zagnieżdżonych zależności klas. Na przykład, jeśli masz następujący kod przy użyciu Depedency Injection.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }

Jeśli wszystkie te zależności zostały załadowane do kontenera IoC, możesz rozwiązać usługę CustomerService, a wszystkie zależności podrzędne zostaną automatycznie rozwiązane.

Na przykład:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}

Ale klasa IoC jest nazywana za pomocą anty-wzorca lokalizatora usług. Czy kontener powinien być przekazywany za pomocą konstruktora DI zamiast wywoływania statycznej T Resolve <T> ()?
Michael Freidgeim

@bendewey Twój przykład sugeruje, że automatycznie przekazuje argumenty do konstruktorów dla nowej usługi CustomerService i nowego CustomerRepository. Czy tak to działa? Co jeśli masz również inne dodatkowe parametry?
Brain2000

28

Jestem fanem deklaratywnego programowania (zobacz, na ile pytań SQL odpowiadam), ale pojemniki IoC, na które patrzyłem, wydają się zbyt tajemnicze dla ich własnego dobra.

... a może twórcy kontenerów IoC nie są w stanie napisać przejrzystej dokumentacji.

... albo oba są prawdziwe w takim czy innym stopniu.

Nie sądzę, że koncepcja kontenera IoC jest zła. Ale implementacja musi być zarówno wydajna (to znaczy elastyczna), aby była użyteczna w wielu różnych aplikacjach, a jednocześnie prosta i łatwa do zrozumienia.

Prawdopodobnie jest ich sześć z pół tuzina innych. Prawdziwa aplikacja (nie zabawka lub wersja demonstracyjna) musi być złożona, biorąc pod uwagę wiele przypadków narożnych i wyjątków od zasad. Albo ujmujesz tę złożoność w kod imperatywny, albo w kod deklaratywny. Ale musisz to gdzieś reprezentować.


5
Podoba mi się twoja obserwacja na temat nieuniknionej obecności złożoności. Różnica między okablowaniem zależnym od kontenera a ręcznym polega na tym, że za pomocą kontenera można skupić się na każdym elemencie, a tym samym zarządzać rosnącą złożonością w dość liniowy sposób. Systemy z ręcznym okablowaniem zależności są trudniejsze do ewolucji, ponieważ czynność konfiguracji jednego komponentu jest trudna do odizolowania od konfiguracji wszystkiego innego.
Nicholas Blumhardt

Lubię AspectJ. To nie jest Java, ale też nie XML. To rodzaj IS Java, ponieważ jest rozszerzeniem tego oprogramowania i wygląda jak Java. Wolę trzymać moje aspekty poza moimi POJO i logiką biznesową. Chciałbym też prawie trzymać moje IoC z dala. W narzędziach do okablowania jest całkowicie za dużo XML, IMHO. Jeśli muszę mieć pliki konfiguracyjne, niech to będą skrypty BeanShell. / Java, zastosuj tutaj swoje wzorce robocze .Net ->.
Chris K

2
Z pewnością rozumiem twoją odpowiedź, ale myślę, że duża część problemu polega na tym, że wiele frameworków IoC (i, naprawdę, wiele innych frameworków również dla innych rzeczy) jest opisanych i udokumentowanych dla innych, którzy już dobrze znają wymagane koncepcje i terminologia. Uczę się dużo o tym, że właśnie odziedziczyłem kod innego programisty. Próbuję zrozumieć, dlaczego kod korzysta z IoC, gdy „wykresy obiektowe” są dość małe i nie ma żadnego rzeczywistego pokrycia testowego kodu. Jako programista SQL prawdopodobnie lepiej docenisz użycie IoC z ORM.
Kenny Evitt,

1
@KennyEvitt, dobra uwaga, wydaje się przedwczesne stosowanie frameworka IoC, jeśli ktoś ma prostą aplikację i nawet nie zadał sobie trudu, by mieć dobry zasięg testu.
Bill Karwin

27

Korzystanie z kontenera polega głównie na zmianie z imperatywnego / skryptowego stylu inicjalizacji i konfiguracji na deklaratywny . Może to mieć kilka różnych korzystnych efektów:

  • Zmniejszenie Hairball rutyny głównego program startowy.
  • Umożliwia dość głębokie możliwości rekonfiguracji w czasie wdrażania.
  • Uczynienie stylu iniekcji zależności ścieżką najmniejszego oporu w nowej pracy.

Oczywiście mogą wystąpić trudności:

  • Kod wymagający złożonego uruchamiania / zamykania / zarządzania cyklem życia może nie być łatwo przystosowany do kontenera.
  • Prawdopodobnie będziesz musiał poruszać wszelkie kwestie osobiste, związane z procesem i kulturą zespołu - ale dlatego zapytałeś ...
  • Niektóre zestawy narzędzi szybko stają się ciężkie, zachęcając do tego rodzaju głębokiej zależności, z którą wiele kontenerów DI zaczęło się jako reakcja.

23

Wydaje mi się, że zbudowałeś już własny kontener IoC (używając różnych wzorców opisanych przez Martina Fowlera) i pytasz, dlaczego implementacja kogoś innego jest lepsza niż twoja.

Tak więc masz sporo kodu, który już działa. Zastanawiasz się, dlaczego chcesz zastąpić go implementacją innej osoby.

Plusy za rozważenie kontenera IoC innej firmy

  • Błędy naprawiane są za darmo
  • Projekt biblioteki może być lepszy niż twój
  • Ludzie mogą już znać tę bibliotekę
  • Biblioteka może być szybsza niż twoja
  • Może mieć pewne funkcje, które chciałbyś wdrożyć, ale nigdy nie miał czasu (czy masz lokalizator usług?)

Cons

  • Dostajesz błędy, za darmo :)
  • Projekt biblioteki może być gorszy niż twój
  • Musisz nauczyć się nowego API
  • Zbyt wiele funkcji, których nigdy nie będziesz używać
  • Zwykle trudniej jest debugować kod, którego nie napisałeś
  • Migracja z poprzedniego kontenera IoC może być uciążliwa

Więc porównaj swoje zalety do swoich wad i podejmij decyzję.


Zgadzam się z tobą Sam - DI jest rodzajem IoC, więc jeśli oryginalny plakat robi DI, już robią IoC, więc prawdziwe pytanie brzmi: po co tworzyć własne.
Jamie Love,

3
Nie zgadzam się. MKOl jest sposobem na DI. Kontenery IOC wykonują DI, przejmując kontrolę nad tworzeniem typu za pomocą dynamicznego odbicia środowiska wykonawczego w celu spełnienia i wstrzyknięcia zależności. OP sugeruje, że robi statyczne DI i zastanawia się, dlaczego musi odejść od korzyści płynących z sprawdzania czasu kompilacji pod kątem korzyści często związanych z dynamicznym odbiciem środowiska wykonawczego MKOl.
Maxm007

17

Myślę, że większość wartości IoC jest uzyskiwana przy użyciu DI. Ponieważ już to robisz, reszta korzyści ma charakter przyrostowy.

Otrzymana wartość będzie zależeć od rodzaju aplikacji, nad którą pracujesz:

  • W przypadku wielu dzierżawców kontener IoC może zająć się kodem infrastruktury do ładowania różnych zasobów klienta. Gdy potrzebujesz komponentu specyficznego dla klienta, użyj niestandardowego selektora, aby obsłużyć logikę i nie martw się o to kodem klienta. Z pewnością możesz to zbudować samodzielnie, ale oto przykład, w jaki sposób IoC może pomóc.

  • Dzięki wielu punktom rozszerzalności IoC może być używany do ładowania komponentów z konfiguracji. Jest to powszechna rzecz do budowania, ale narzędzia są dostarczane przez kontener.

  • Jeśli chcesz użyć AOP do niektórych przekrojowych problemów, IoC zapewnia haki do przechwytywania wywołań metod. Rzadziej robi się to ad hoc w projektach, ale IoC ułatwia.

Pisałem wcześniej taką funkcjonalność, ale jeśli teraz potrzebuję którejś z tych funkcji, wolę użyć gotowego i przetestowanego narzędzia, jeśli pasuje do mojej architektury.

Jak wspomnieli inni, możesz również centralnie skonfigurować, których klas chcesz używać. Chociaż może to być dobra rzecz, wiąże się to z pewnym błędem i komplikacją. Podstawowe komponenty do większości aplikacji nie są bardzo wymieniane, więc kompromis jest nieco trudniejszy.

Korzystam z kontenera IoC i doceniam jego funkcjonalność, ale muszę przyznać, że zauważyłem kompromis: mój kod staje się bardziej przejrzysty na poziomie klasy i mniej wyraźny na poziomie aplikacji (tj. Wizualizacja przepływu sterowania).


16

Jestem uzależniającym się z MKOl. Trudno mi obecnie usprawiedliwić używanie IOC dla DI w większości przypadków. Kontenery MKOl poświęcają sprawdzanie czasu kompilacji, a rzekomo w zamian zapewniają „łatwą” konfigurację, kompleksowe zarządzanie cyklem życia i odkrywanie zależności w czasie rzeczywistym. Uważam, że utrata sprawdzania czasu kompilacji i wynikająca z tego magia / wyjątki w czasie wykonywania nie są warte dzwonków i gwizdów w zdecydowanej większości przypadków. W aplikacjach dla dużych przedsiębiorstw bardzo utrudniają śledzenie tego, co się dzieje.

Nie kupuję argumentu centralizacji, ponieważ można bardzo łatwo scentralizować konfigurację statyczną, używając fabryki abstrakcyjnej dla swojej aplikacji i religijnie odraczając tworzenie obiektów do fabryki abstrakcyjnej, tj. Wykonując odpowiednie DI.

Dlaczego nie wykonać statycznego, pozbawionego magii DI takiego jak ten:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}

Konfiguracja kontenera jest abstrakcyjną implementacją fabryki, rejestracje są implementacjami abstrakcyjnych elementów członkowskich. Jeśli potrzebujesz nowej zależności singleton, po prostu dodaj inną właściwość abstrakcyjną do fabryki abstrakcyjnych. Jeśli potrzebujesz przejściowej zależności, po prostu dodaj inną metodę i wstrzyknij ją jako Func <>.

Zalety:

  • Cała konfiguracja i konfiguracja tworzenia obiektów jest scentralizowana.
  • Konfiguracja to tylko kod
  • Sprawdzanie czasu kompilacji ułatwia utrzymanie, ponieważ nie można zapomnieć o aktualizacji rejestracji.
  • Brak magii odbicia w czasie wykonywania

Polecam sceptykom, aby wypróbowali kolejny projekt zielonego pola i uczciwie zadaję sobie pytanie, w którym momencie potrzebujesz pojemnika. Łatwo jest później dodać kontener IOC, ponieważ właśnie zastępujesz fabryczną implementację modułem konfiguracji kontenera IOC.


Nie wydaje mi się to argumentem przeciwko IoC, ale argumentem przeciwko konfigurowalnym ramom IoC. Czy wasze fabryki tutaj nie sprowadzają się tylko do prostych, zakodowanych w Internecie IoC?
Mr.Mindor,

Myślę, że kiedy plakat wspomniał o IoC, miał na myśli szkielet kontenerowy IoC. Dzięki za oryginalny tekst pomaga mi to przekonać się, że nie mam frameworka. Więc jeśli można go konfigurować w testach i kodzie produkcyjnym, jaka jest zaleta frameworku „konfigurowalnego”?
huggie

Z mojego rozumienia terminologii Inversion Of Control oznacza znajdowanie zależności w czasie wykonywania. Tak, można powiedzieć, że fabryka to zasadniczo zakodowany lub statyczny kontener, ale nie jest to kontener MKOl. Typy są rozwiązywane w czasie kompilacji. Jest to argument przeciwko używaniu konfigurowalnych ram, które używają wyszukiwania słownikowego do rozwiązywania typów w czasie wykonywania.
Maxm007,

14

Największą zaletą korzystania z kontenerów IoC dla mnie (osobiście używam Ninject) było wyeliminowanie przekazywania ustawień i innych rodzajów globalnych obiektów stanu.

Nie programuję dla Internetu, mój jest aplikacją konsolową i w wielu miejscach głęboko w drzewie obiektów muszę mieć dostęp do ustawień lub metadanych określonych przez użytkownika, które są tworzone w całkowicie oddzielnej gałęzi drzewa obiektów. Dzięki IoC po prostu mówię Ninject, aby traktował Ustawienia jako singleton (ponieważ zawsze jest tylko jedna ich instancja), poprosił o Ustawienia lub Słownik w konstruktorze i presto ... pojawiają się magicznie, kiedy ich potrzebuję!

Bez korzystania z kontenera IoC musiałbym przekazać ustawienia i / lub metadane w dół przez 2, 3, ..., n obiektów, zanim byłby faktycznie używany przez obiekt, który go potrzebował.

Kontenery DI / IoC mają wiele innych zalet, ponieważ inne osoby opisały tu szczegółowo, a przejście od pomysłu tworzenia obiektów do zamawiania obiektów może być zaskakujące, ale użycie DI było bardzo pomocne dla mnie i mojego zespołu, więc może możesz to dodać do twojego arsenału!


2
To ogromny plus. Nie mogę uwierzyć, że nikt wcześniej o tym nie wspominał. Bez potrzeby przekazywania lub przechowywania obiektów tempo powoduje, że kod jest znacznie czystszy.
Finglas,

1
Globalne obiekty @qble działają świetnie ... jeśli chcesz, aby baza kodu była ściśle powiązanym, niemożliwym do przetestowania koszmarem, który z czasem staje się coraz trudniejszy do modyfikacji. Powodzenia z tym.
Jeffrey Cameron,

2
@JeffreyCameron IoC Container jest obiektem globalnym. Nie usuwa twoich globalnych zależności.
qble

3
To prawda, jednak kontener IoC jest przezroczysty dla reszty aplikacji. Nazywasz go raz przy uruchamianiu, aby uzyskać instancję, a potem to ostatni raz, kiedy go widzisz. W przypadku obiektów globalnych Twój kod jest zaśmiecony silnymi zależnościami
Jeffrey Cameron

1
@qble wykorzystuje fabryki do tworzenia przejściowych instancji w locie. Fabryki są usługami singleton. Jeśli instancje przejściowe potrzebują czegoś z kontenera IOC, podłącz je do fabryki i poproś fabrykę o przekazanie zależności do instancji przejściowej.
Jeffrey Cameron,

14

Ramy IoC są doskonałe, jeśli chcesz ...

  • ... wyrzucić bezpieczeństwo typu. Wiele (wszystkich?) Struktur IoC zmusza do wykonania kodu, jeśli chcesz mieć pewność, że wszystko jest poprawnie podłączone. „Hej! Mam nadzieję, że wszystko skonfigurowałem, więc moje inicjalizacje tych 100 klas nie zawiodą w produkcji, z wyjątkami zerowymi!”

  • ... miot kod z globalnych (ramy IoC są wszystkim o zmianę stanów globalnych).

  • ... pisz kiepski kod z niejasnymi zależnościami, które trudno refaktoryzować, ponieważ nigdy nie dowiesz się, od czego zależy.

Problem z IoC polega na tym, że ludzie, którzy ich używają, pisali taki kod

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

co jest oczywiście wadliwe, ponieważ zależność między Foo a Barem jest sztywna. Potem zdali sobie sprawę, że lepiej byłoby napisać podobny kod

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

co również jest wadliwe, ale mniej oczywiste. W Haskell Foo()byłby to typ , IO Fooale tak naprawdę nie chcesz opcji IO-part i powinien to być znak ostrzegawczy, że coś jest nie tak z twoim projektem, jeśli go masz.

Aby się go pozbyć (część IO), uzyskaj wszystkie zalety frameworków IoC i żadnej z jego wad można zamiast tego użyć abstrakcyjnej fabryki.

Prawidłowe rozwiązanie byłoby podobne

data Foo = Foo { apa :: Bar }

albo może

data Foo = forall b. (IBar b) => Foo { apa :: b }

i wstrzyknąć (ale nie nazwałbym tego wstrzyknięciem) Bar.

Zobacz też: zobacz ten film z Erikiem Meijerem (wynalazcą LINQ), w którym mówi on, że DI jest dla ludzi, którzy nie znają matematyki (i nie mogłem się zgodzić więcej): http://www.youtube.com/watch?v = 8 Mttjyf-8P4

W przeciwieństwie do pana Spolsky'ego nie wierzę, że ludzie, którzy korzystają ze struktur IoC, są bardzo inteligentni - po prostu wierzę, że nie znają matematyki.


9
odrzuć bezpieczeństwo typu - z tym wyjątkiem, że twój przykład jest nadal bezpieczny dla typu, taki jest interfejs. Nie przekazujesz typów obiektów, ale typ interfejsu. Bezpieczeństwo typu i tak nie eliminuje wyjątków zerowych referencji, możesz z przyjemnością przekazać referencje zerowe jako bezpieczne parametry typu - to wcale nie jest problem z bezpieczeństwem typów.
blowdart

Wyrzucasz bezpieczeństwo typu, ponieważ nie wiesz, czy ten typ jest podłączony do kontenera IoC, czy nie (typ IoC <Bar> nie jest IBar, tak naprawdę jest IO IBar). I tak, w przykładzie Haskella Nie mogę uzyskać zerowego odniesienia.
finnsson

Bezpieczeństwo typu nie jest tak naprawdę związane z podłączeniem obiektu, przynajmniej nie w Javie / C #, chodzi po prostu o to, że obiekt jest odpowiedniego typu. I ConcreteClass myClass = null jest nadal odpowiedniego typu. Jeśli chcesz wyegzekwować wartość inną niż zero, wówczas wchodzą w grę umowy kodowe.
blowdart

2
W pewnym stopniu zgadzam się z twoim pierwszym punktem, chciałbym wyjaśnienia drugiego, a trzeci nigdy nie był dla mnie problemem. Próbka w języku C # używa kontenera IoC jako lokalizatora usług, co jest częstym błędem. I porównanie C # vs Haskell = jabłka vs pomarańcze.
Mauricio Scheffer

2
Nie wyrzucasz bezpieczeństwa typu. Niektóre (jeśli nie większość) pojemniki DI umożliwiają weryfikację pełnego wykresu obiektowego po procesie rejestracji. Robiąc to, możesz zapobiec uruchomieniu aplikacji przy błędnej konfiguracji (szybko się nie powiedzie), a moje aplikacje zawsze mają kilka testów jednostkowych, które wywołują konfigurację produkcyjną, aby umożliwić mi znalezienie tych błędów podczas testowania. Radziłbym wszystkim, którzy używają kontenera IoC, aby napisali kilka testów jednostkowych, które upewnią się, że wszystkie obiekty najwyższego poziomu można rozwiązać.
Steven

10

Przekonałem się, że prawidłowe wdrożenie Dependency Injection zmusza programistów do korzystania z różnych innych praktyk programistycznych, które pomagają poprawić testowalność, elastyczność, łatwość konserwacji i skalowalność kodu: praktyki takie jak zasada pojedynczej odpowiedzialności, rozdzielenie problemów i kodowanie przeciwko interfejsom API. Wydaje mi się, że jestem zmuszony pisać bardziej modularne klasy i metody wielkości kęsa, co sprawia, że ​​kod jest łatwiejszy do odczytania, ponieważ można go wziąć w kęsy wielkości kęsa.

Ale ma też tendencję do tworzenia raczej dużych drzew zależności, które są znacznie łatwiejsze do zarządzania za pomocą frameworka (szczególnie jeśli używasz konwencji) niż ręcznie. Dzisiaj chciałem przetestować coś naprawdę szybko w LINQPad i pomyślałem, że byłoby zbyt kłopotliwe, aby stworzyć jądro i załadować moje moduły, i ostatecznie napisałem to ręcznie:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

Z perspektywy czasu szybsze byłoby użycie frameworka IoC, ponieważ moduły definiują prawie wszystkie te rzeczy zgodnie z konwencją.

Po spędzeniu czasu na studiowaniu odpowiedzi i komentarzy na to pytanie jestem przekonany, że ludzie, którzy są przeciwni korzystaniu z kontenera IoC, nie praktykują prawdziwego wstrzykiwania zależności. Przykłady, które widziałem, to praktyki, które są często mylone z zastrzykiem zależności. Niektóre osoby narzekają na trudności z „odczytaniem” kodu. Prawidłowe wykonanie większości kodu powinno być identyczne przy ręcznym korzystaniu z DI, jak przy korzystaniu z kontenera IoC. Różnica powinna polegać całkowicie na kilku „punktach uruchamiania” w aplikacji.

Innymi słowy, jeśli nie lubisz kontenerów IoC, prawdopodobnie nie robisz Dependency Injection w sposób, w jaki powinien to być zrobiony.

Kolejna kwestia: wstrzyknięcia zależności naprawdę nie można wykonać ręcznie, jeśli używasz odbicia w dowolnym miejscu. Chociaż nienawidzę tego, co robi odbicie w nawigacji kodowej, musisz zdawać sobie sprawę, że istnieją pewne obszary, w których tak naprawdę nie można tego uniknąć. Na przykład program ASP.NET MVC próbuje utworzyć instancję kontrolera poprzez odbicie na każde żądanie. Aby wykonać wstrzyknięcie zależności ręcznie, trzeba uczynić każdy kontroler „kontekstowym katalogiem głównym”:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

Porównaj to teraz z zezwoleniem na to dla środowiska DI:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

Za pomocą środowiska DI zwróć uwagę, że:

  • Mogę przetestować jednostkę w tej klasie. Tworząc próbnyISimpleWorkflowInstanceMerger , mogę przetestować, czy została wykorzystana tak, jak się spodziewam, bez potrzeby połączenia z bazą danych ani nic takiego.
  • Używam znacznie mniej kodu, a kod jest znacznie łatwiejszy do odczytania.
  • Jeśli jedna ze zmian zależności mojej zależności, nie muszę wprowadzać żadnych zmian w kontrolerze. Jest to szczególnie miłe, gdy weźmie się pod uwagę, że wiele kontrolerów może wykorzystywać niektóre z tych samych zależności.
  • Nigdy nie odwołuję się wyraźnie do klas z mojej warstwy danych. Moja aplikacja internetowa może po prostu zawierać odniesienie do projektu zawierającego ISimpleWorkflowInstanceMergerinterfejs. To pozwala mi podzielić aplikację na osobne moduły i zachować prawdziwą architekturę wielowarstwową, co z kolei sprawia, że ​​rzeczy są znacznie bardziej elastyczne.

Typowa aplikacja internetowa będzie miała sporo kontrolerów. Cały ból związany z ręcznym wykonywaniem DI w każdym kontrolerze naprawdę się powiększy wraz z rozwojem aplikacji. Jeśli masz aplikację z tylko jednym kontekstowym katalogiem głównym, który nigdy nie próbuje utworzyć instancji usługi przez odbicie, to nie jest to tak duży problem. Niemniej jednak każda aplikacja, która korzysta z Dependency Injection, stanie się wyjątkowo kosztowna w zarządzaniu, gdy osiągnie określony rozmiar, chyba że użyjesz jakiegoś szkieletu do zarządzania wykresem zależności.


9

Ilekroć używasz słowa kluczowego „new”, tworzysz konkretną zależność od klasy i mały dzwonek alarmowy powinien rozbrzmiewać w twojej głowie. Trudniej jest przetestować ten obiekt w izolacji. Rozwiązaniem jest zaprogramowanie interfejsów i wstrzyknięcie zależności, aby obiekt mógł być testowany jednostkowo za pomocą wszystkiego, co implementuje ten interfejs (np. Makiety).

Problem polega na tym, że musisz gdzieś budować obiekty. Wzorzec fabryczny to jeden ze sposobów przeniesienia sprzężenia z POXO (zwykły stary „wstaw tutaj swój język OO” Obiekty). Jeśli ty i twoi współpracownicy piszecie taki kod, to kontener IoC jest kolejnym „przyrostowym ulepszeniem”, które możecie wprowadzić do swojej bazy kodu. Przeniesie cały ten paskudny fabryczny kod z czystych obiektów i logiki biznesowej. Zdobędą go i pokochają. Do licha, powiedz firmie, dlaczego to uwielbiasz i zachęć wszystkich.

Jeśli twoi współpracownicy jeszcze nie robią DI, sugeruję, abyś najpierw skupił się na tym. Przekaż, jak napisać czysty kod, który można łatwo przetestować. Czysty kod DI jest trudną częścią, gdy już tam będziesz, przeniesienie logiki okablowania obiektu z klas Factory do kontenera IoC powinno być stosunkowo proste.


8

Ponieważ wszystkie zależności są wyraźnie widoczne, promuje tworzenie komponentów, które są luźno powiązane, a jednocześnie łatwo dostępne i wielokrotnego użytku w całej aplikacji.


7

Nie potrzebujesz kontenera IoC.

Ale jeśli rygorystycznie przestrzegasz wzorca DI, zauważysz, że posiadanie go usunie mnóstwo zbędnego, nudnego kodu.

W każdym razie często jest to najlepszy czas na użycie biblioteki / frameworka - gdy rozumiesz, co on robi i możesz to zrobić bez biblioteki.


6

Tak się składa, że ​​próbuję wyhodować domowy kod DI i zastąpić go MKOl. Prawdopodobnie usunąłem ponad 200 wierszy kodu i zastąpiłem go około 10. Tak, musiałem trochę się nauczyć, jak korzystać z kontenera (Winsor), ale jestem inżynierem pracującym nad technologiami internetowymi w 21 wieku, więc jestem do tego przyzwyczajony. Prawdopodobnie spędziłem około 20 minut, zastanawiając się, jak to zrobić. To było warte mojego czasu.


3
„ale jestem inżynierem pracującym nad technologiami internetowymi w XXI wieku, więc jestem do tego przyzwyczajony” -> +1
user96403

5

W miarę rozdzielania klas i odwracania zależności, klasy pozostają małe, a „wykres zależności” wciąż rośnie. (To nie jest złe.) Korzystanie z podstawowych funkcji kontenera IoC sprawia, że ​​okablowanie wszystkich tych obiektów jest banalne, ale wykonanie go ręcznie może być bardzo uciążliwe. Na przykład, co jeśli chcę utworzyć nową instancję „Foo”, ale potrzebuje ona „paska”. I „Bar” potrzebuje „A”, „B” i „C”. I każda z nich potrzebuje 3 innych rzeczy itp. (Tak, nie mogę wymyślić dobrych fałszywych nazwisk :)).

Użycie kontenera IoC do zbudowania grafu obiektowego zmniejsza złożoność tony i wypycha ją do jednorazowej konfiguracji. Mówię po prostu „stwórz mi Foo”, a to wyjaśnia, co jest potrzebne, aby je zbudować.

Niektóre osoby używają kontenerów IoC do znacznie większej infrastruktury, co jest dobre w przypadku zaawansowanych scenariuszy, ale w tych przypadkach zgadzam się, że może zaciemniać i utrudniać czytanie kodu oraz debugowanie dla nowych deweloperów.


1
dlaczego nadal łagodzimy najniższy wspólny mianownik, zamiast pracować nad jego podniesieniem?
stevenharman

4
Nie mówiłem nic o łagodzeniu. Powiedziałem tylko, że zaawansowane scenariusze mogą sprawić, że dla nowych twórców sprawy będą bardziej zaciemnione - to fakt. Nie powiedziałem „więc nie rób tego”. Ale nawet wtedy (czas na adwokata diabła) są często inne sposoby osiągnięcia tego samego, co czyni bazę kodu bardziej wyraźną i przystępną. Ukrywanie złożoności niestandardowej konfiguracji narzędzia infrastruktury tylko dlatego, że nie jest to właściwy powód, i nikogo nie przyciąga.
Aaron Lerch

4

Dittos o Jedności. Stań się zbyt duży i usłyszysz skrzypienie w krokwiach.

Nigdy nie zaskakuje mnie, gdy ludzie zaczynają mówić o tym, jak czysty wygląd kodu IoC to ci sami ludzie, którzy kiedyś mówili o tym, jak szablony w C ++ były eleganckim sposobem na powrót w latach 90., ale obecnie będą je potępiać jako tajemnicze . Bah!


2

W świecie .NET AOP nie jest zbyt popularny, więc dla DI platforma jest Twoją jedyną realną opcją, niezależnie od tego, czy piszesz ją samodzielnie, czy używasz innej.

Jeśli użyłeś AOP, możesz wstrzykiwać podczas kompilacji aplikacji, co jest bardziej powszechne w Javie.

DI ma wiele zalet, takich jak zmniejszone sprzężenie, więc testowanie jednostek jest łatwiejsze, ale jak to zaimplementować? Czy chcesz użyć refleksji, aby zrobić to sam?


Nowa struktura MEF pozwala właśnie na to .NET i muszę powiedzieć, że kod jest czystszy i łatwiejszy do zrozumienia, znacznie ułatwia zrozumienie koncepcji DI.
terjetyl

4
@TT: MEF nie jest pojemnikiem do wstrzykiwania zależności i nie ma nic wspólnego z AOP.
Mauricio Scheffer

2

Więc minęły prawie 3 lata, co?

  1. 50% osób, które komentowały ramy IoC, nie rozumie różnicy między IoC a IoC Framework. Wątpię, aby wiedzieli, że możesz pisać kod bez wdrażania na serwerze aplikacji

  2. Jeśli weźmiemy najpopularniejszy framework Java Spring, to konfiguracja IoC została przeniesiona z XMl do kodu i teraz wygląda tak

`@Configuracja klasy publicznej AppConfig {

public @Bean TransferService transferService() {
    return new TransferServiceImpl(accountRepository());
}

public @Bean AccountRepository accountRepository() {
    return new InMemoryAccountRepository();
}

} `I potrzebujemy ram, aby to zrobić, dlaczego dokładnie?


1

Szczerze mówiąc, nie widzę wielu przypadków, w których potrzebne są pojemniki IoC, i przez większość czasu po prostu dodają niepotrzebnej złożoności.

Jeśli używasz go tylko do uproszczenia budowy obiektu, musiałbym zapytać, czy tworzysz ten obiekt w więcej niż jednej lokalizacji? Czy singleton nie spełni twoich potrzeb? Czy zmieniasz konfigurację w czasie wykonywania? (Przełączanie typów źródeł danych itp.).

Jeśli tak, może być potrzebny kontener IoC. Jeśli nie, to po prostu odsuwasz inicjalizację od miejsca, w którym programista może ją łatwo zobaczyć.

Kto powiedział, że interfejs jest lepszy niż dziedziczenie? Załóżmy, że testujesz usługę. Dlaczego nie skorzystać z konstruktora DI i stworzyć próbne zależności za pomocą dziedziczenia? Większość usług, z których korzystam, ma tylko kilka zależności. Wykonanie testów jednostkowych w ten sposób zapobiega zachowaniu mnóstwa bezużytecznych interfejsów i oznacza, że ​​nie musisz używać Resharpera, aby szybko znaleźć deklarację metody.

Uważam, że w przypadku większości implementacji stwierdzenie, że kontenery IoC usuwają niepotrzebny kod, jest mitem.

Po pierwsze, najpierw trzeba ustawić pojemnik. Następnie musisz zdefiniować każdy obiekt, który należy zainicjować. Aby nie zapisywać kodu podczas inicjalizacji, należy go przenieść (chyba że obiekt jest używany więcej niż jeden raz. Czy jest lepszy jako Singleton?). Następnie dla każdego obiektu zainicjowanego w ten sposób musisz utworzyć i utrzymywać interfejs.

Ktoś ma jakieś przemyślenia na ten temat?


Nawet w przypadku małych projektów umieszczenie konfiguracji w języku XML sprawia, że ​​jest ona o wiele czystsza i bardziej modułowa - i po raz pierwszy sprawia, że ​​kod można ponownie wykorzystać (ponieważ nie jest już zanieczyszczony klejem). Dla odpowiednich produktów oprogramowania, można skonfigurować lub zamiana na przykład ShippingCostCalculatoralbo ProductUIPresentation, albo jakiejkolwiek innej strategii / realizacji, w dokładnie tym samym kodzie , poprzez wprowadzenie tylko jednego zmieniony plik XML.
Thomas W

Jeśli zostanie wdrożony nie chcąc, jako ogólną praktykę, myślę, że dodajesz więcej złożoności, a nie mniej. Gdy potrzebujesz zamienić coś w czasie wykonywania, są świetne, ale po co dodawać zaciemnianie? Po co utrzymywać nadmiar dodatkowych interfejsów dla rzeczy, które nigdy nie zostaną wyłączone? Nie mówię, że nie używaj IoC do testowania. W każdym razie użyj zastrzyku zależności od właściwości, a nawet konstruktora. Czy potrzebujesz interfejsu? Dlaczego nie podklasować obiektów testowych? Czy to nie byłoby czystsze?
Fred

W ogóle nie potrzebujesz interfejsów do konfiguracji XML / IoC. Znalazłem wielką poprawę przejrzystości w małym projekcie z tylko ~ 8 stronami i 5 lub 6 usługami. Jest mniej zaciemnienia, ponieważ usługi i kontrolery nie potrzebują już kodu kleju.
Thomas W

1

Potrzebujesz kontenera IoC, jeśli potrzebujesz scentralizować konfigurację swoich zależności, aby można je było łatwo wymieniać masowo. Jest to najbardziej sensowne w TDD, gdzie wiele zależności jest zamienionych, ale gdzie istnieje niewielka współzależność między zależnościami. Odbywa się to kosztem do pewnego stopnia zaciemnienia przepływu kontroli nad budową obiektów, dlatego ważna jest dobrze zorganizowana i odpowiednio udokumentowana konfiguracja. Dobrze jest mieć powód, aby to zrobić, w przeciwnym razie jest to jedynie pozłacanie abstrakcyjne . Widziałem to tak źle, że zostało sprowadzone do bycia odpowiednikiem instrukcji goto dla konstruktorów.


1

Oto dlaczego. Projekt nazywa się IOC-with-Ninject. Możesz pobrać i uruchomić go za pomocą Visual Studio. W tym przykładzie użyto Ninject, ale WSZYSTKIE instrukcje „new” znajdują się w jednym miejscu i można całkowicie zmienić sposób działania aplikacji, zmieniając moduł powiązania, który ma być używany. Przykład skonfigurowano tak, aby można było powiązać z próbną wersją usług lub wersją rzeczywistą. W małych projektach może to nie mieć znaczenia, ale w dużych projektach jest to wielka sprawa.

Dla jasności zalety, jakie widzę: 1) WSZYSTKIE nowe instrukcje w jednym miejscu w katalogu głównym kodu. 2) Całkowicie prześlij ponownie kod z jedną zmianą. 3) Dodatkowe punkty za „fajny czynnik”, ponieważ to ... no cóż: fajne. : p


1

Spróbuję dowiedzieć się, dlaczego MKOl może nie być dobry z mojej perspektywy.

Podobnie jak w przypadku wszystkich innych elementów, kontener IOC (lub, jak ująłby to Einstein, I = OC ^ 2) to koncepcja, którą musisz sam zdecydować, czy jej potrzebujesz, czy nie w swoim kodzie. Ostatnie oburzenie mody o MKOl to tylko moda. Nie zakochuj się w modzie, to pierwsze. Istnieje mnóstwo koncepcji, które można zaimplementować w kodzie. Przede wszystkim używam wstrzykiwania zależności, odkąd zacząłem programować i sam nauczyłem się tego terminu, kiedy został spopularyzowany pod tą nazwą. Kontrola zależności jest bardzo starym tematem i do tej pory poruszano ją trylionami sposobów, w zależności od tego, co było oddzielone od czego. Oddzielenie wszystkiego od wszystkiego to nonsens. Problem z kontenerem IOC polega na tym, że próbuje być tak przydatny jak Entity Framework lub NHibernate. Podczas gdy pisanie odwzorowania obiektowo-relacyjnego jest po prostu koniecznością, gdy tylko trzeba połączyć dowolną bazę danych z systemem, kontener IOC nie zawsze jest konieczny. Więc kiedy kontener IOC jest przydatny:

  1. Gdy masz sytuację z wieloma zależnościami, które chcesz zorganizować
  2. Gdy nie zależy Ci na połączeniu kodu z produktem innej firmy
  3. Gdy Twoi programiści chcą dowiedzieć się, jak pracować z nowym narzędziem

1: Nie jest tak często, że masz tak wiele zależności w kodzie, albo że znasz je na wczesnym etapie projektowania. Myślenie abstrakcyjne jest przydatne, gdy należy myśleć abstrakcyjnie.

2: Łączenie kodu z kodem innej firmy stanowi problem HuGe. Pracowałem z kodem, który ma ponad 10 lat i podążałem w tym czasie za fantazyjnymi i zaawansowanymi koncepcjami ATL, COM, COM + i tak dalej. Teraz nie można nic zrobić z tym kodem. Mówię o tym, że zaawansowana koncepcja daje wyraźną przewagę, ale na dłuższą metę jest ona anulowana z samą przestarzałą przewagą. Po prostu wszystko to uczyniło droższym.

3: Tworzenie oprogramowania jest wystarczająco trudne. Możesz rozszerzyć go na nierozpoznawalne poziomy, jeśli pozwolisz, aby jakiś zaawansowany pomysł pojawił się w kodzie. Wystąpił problem z IOC2. Chociaż jest to zależność odsprzęgająca, oddziela także przepływ logiczny. Wyobraź sobie, że znalazłeś błąd i musisz zrobić sobie przerwę, aby zbadać sytuację. IOC2, jak każda inna zaawansowana koncepcja, utrudnia to. Naprawienie błędu w ramach koncepcji jest trudniejsze niż naprawienie błędu w prostszym kodzie, ponieważ po naprawieniu błędu należy ponownie zastosować się do koncepcji. (Aby dać przykład, C ++ .NET ciągle zmienia składnię tak bardzo, że musisz się zastanowić, zanim zmienisz wersję starszej wersji .NET.) Więc w czym problem z IOC? Problem polega na rozwiązywaniu zależności. Logika rozwiązywania jest zwykle ukryta w samym IOC2, napisane może w niecodzienny sposób, którego musisz się nauczyć i utrzymać. Czy Twój produkt innej firmy będzie dostępny za 5 lat? Microsoft nie był.

Syndrom „Wiemy jak” jest napisany wszędzie w odniesieniu do IOC2. Jest to podobne do testowania automatyzacji. Fantazyjny termin i idealne rozwiązanie na pierwszy rzut oka, wystarczy, że wszystkie testy wykonasz w nocy i zobaczysz wyniki rano. Naprawdę bolesne jest wyjaśnianie firmie po firmie, co tak naprawdę oznaczają testy automatyczne. Zautomatyzowane testowanie zdecydowanie nie jest szybkim sposobem na zmniejszenie liczby błędów, które możesz wprowadzić z dnia na dzień, aby podnieść jakość swojego produktu. Ale moda sprawia, że ​​to pojęcie jest denerwująco dominujące. IOC2 cierpi na ten sam zespół. Uważa się, że należy go wdrożyć, aby oprogramowanie było dobre. KAŻDY ostatni wywiad Zostałem zapytany, czy wdrażam IOC2 i automatyzację. To znak mody: firma miała część kodu napisanego w MFC, której nie porzuci.

Musisz nauczyć się IOC2 jak każdej innej koncepcji oprogramowania. Decyzja, czy należy użyć IOC2, należy do zespołu i firmy. Jednak przed podjęciem decyzji należy podać przynajmniej WSZYSTKIE powyższe argumenty. Tylko jeśli widzisz, że strona plus przewyższa stronę negatywną, możesz podjąć pozytywną decyzję.

Nie ma nic złego w IOC2, z wyjątkiem tego, że rozwiązuje tylko problemy, które rozwiązuje i wprowadza problemy, które wprowadza. Nic więcej. Jednak przeciwstawianie się modzie jest bardzo trudne, mają usta potu, wyznawców wszystkiego. Dziwne, jak nikogo nie ma, kiedy ujawnia się problem z ich fantazją. Broniono wielu koncepcji w branży oprogramowania, ponieważ generują zysk, powstają książki, odbywają się konferencje, powstają nowe produkty. To moda, zwykle krótkotrwała. Gdy tylko ludzie znajdą coś innego, porzucają to całkowicie. IOC2 jest użyteczny, ale wykazuje te same znaki, co wiele innych zaginionych koncepcji, które widziałem. Nie wiem, czy to przetrwa. Nie ma na to żadnej reguły. Myślisz, że jeśli będzie przydatny, przetrwa. Nie, nie idzie tak. Wystarczy jedna duża, bogata firma, a koncepcja może umrzeć w ciągu kilku tygodni. Zobaczymy. NHibernate przeżył, EF zajął drugie miejsce. Może IOC2 też przetrwa. Nie zapominaj, że większość pojęć w tworzeniu oprogramowania nie jest niczym szczególnym, są one bardzo logiczne, proste i oczywiste, a czasem trudniej jest zapamiętać obecną konwencję nazewnictwa niż zrozumieć samą koncepcję. Czy znajomość IOC2 czyni programistę lepszym programistą? Nie, ponieważ jeśli programista nie byłby w stanie wymyślić koncepcji podobnej do IOC2, wówczas trudno będzie mu zrozumieć, który problem rozwiązuje IOC2, korzystanie z niego będzie wyglądać sztucznie i może zacząć z niego korzystać ze względu na polityczną poprawność. Nie zapominaj, że większość pojęć w tworzeniu oprogramowania nie jest niczym szczególnym, są one bardzo logiczne, proste i oczywiste, a czasem trudniej jest zapamiętać obecną konwencję nazewnictwa niż zrozumieć samą koncepcję. Czy znajomość IOC2 czyni programistę lepszym programistą? Nie, ponieważ jeśli programista nie byłby w stanie wymyślić koncepcji podobnej do IOC2, wówczas trudno będzie mu zrozumieć, który problem rozwiązuje IOC2, korzystanie z niego będzie wyglądać sztucznie i może zacząć z niego korzystać ze względu na polityczną poprawność. Nie zapominaj, że większość pojęć w tworzeniu oprogramowania nie jest niczym szczególnym, są one bardzo logiczne, proste i oczywiste, a czasem trudniej jest zapamiętać obecną konwencję nazewnictwa niż zrozumieć samą koncepcję. Czy znajomość IOC2 czyni programistę lepszym programistą? Nie, ponieważ jeśli programista nie byłby w stanie wymyślić koncepcji podobnej do IOC2, wówczas trudno będzie mu zrozumieć, który problem rozwiązuje IOC2, korzystanie z niego będzie wyglądać sztucznie i może zacząć z niego korzystać ze względu na polityczną poprawność. Czy znajomość IOC2 czyni programistę lepszym programistą? Nie, ponieważ jeśli programista nie byłby w stanie wymyślić koncepcji podobnej do IOC2, wówczas trudno będzie mu zrozumieć, który problem rozwiązuje IOC2, korzystanie z niego będzie wyglądać sztucznie i może zacząć z niego korzystać ze względu na polityczną poprawność. Czy znajomość IOC2 czyni programistę lepszym programistą? Nie, ponieważ jeśli programista nie byłby w stanie wymyślić koncepcji podobnej do IOC2, wówczas trudno będzie mu zrozumieć, który problem rozwiązuje IOC2, korzystanie z niego będzie wyglądać sztucznie i może zacząć z niego korzystać ze względu na polityczną poprawność.


0

Osobiście używam IoC jako pewnego rodzaju mapy struktury mojej aplikacji (tak, wolę też StructureMap;)). Ułatwia to zastąpienie moich zwykłych implementacji interfejsu implementacjami Moq podczas testów. Utworzenie konfiguracji testowej może być tak proste, jak wykonanie nowego wywołania init do mojej struktury IoC, zastępując dowolną klasę moją granicą testową próbną.

Prawdopodobnie nie jest to po to, aby korzystać z IoC, ale używam go najczęściej ...




0

Zdaję sobie sprawę, że jest to dość stary post, ale wydaje się, że wciąż jest dość aktywny, i pomyślałem, że przedstawię kilka kwestii, które nie zostały jeszcze wspomniane w innych odpowiedziach.

Powiem, że zgadzam się z zaletami wstrzykiwania zależności, ale wolę samodzielnie konstruować i zarządzać obiektami, używając wzorca podobnego do tego, który zarysował Maxm007 w swojej odpowiedzi. Znalazłem dwa główne problemy z używaniem kontenerów innych firm:

1) Posługiwanie się biblioteką innej firmy „automatycznym” zarządzaniem żywotnością obiektów może przynieść nieoczekiwane rezultaty. Odkryliśmy, że szczególnie w dużych projektach możesz mieć znacznie więcej kopii obiektu, niż się spodziewasz, a nawet więcej, niż gdybyś ręcznie zarządzał cyklami życia. Jestem pewien, że różni się to w zależności od używanego frameworka, ale problem jednak istnieje. Może to być również problematyczne, jeśli obiekt przechowuje zasoby, połączenia danych itp., Ponieważ obiekt może czasami żyć dłużej, niż się spodziewasz. Dlatego nieuchronnie kontenery IoC mają tendencję do zwiększania wykorzystania zasobów i wykorzystania pamięci przez aplikację.

2) Moim zdaniem kontenery IoC są formą „programowania czarnej skrzynki”. Odkryłem, że w szczególności nasi mniej doświadczeni programiści mają tendencję do ich nadużywania. Pozwala to programiście nie myśleć o tym, jak obiekty powinny się ze sobą odnosić ani jak je rozdzielić, ponieważ zapewnia im mechanizm, w którym mogą po prostu złapać dowolny obiekt z powietrza. Np. Może istnieć dobry powód projektowy, że ObjectA nigdy nie powinien wiedzieć o ObjectB bezpośrednio, ale zamiast tworzyć fabrykę, most lub lokalizator usług, niedoświadczony programista po prostu powie „nie ma problemu, po prostu wezmę ObjectB z kontenera IoC „. Może to faktycznie prowadzić do zwiększonego sprzężenia obiektów, co właśnie ma zapobiegać IoC.


-8

Wstrzykiwanie zależności w projekcie ASP.NET można wykonać za pomocą kilku wierszy kodu. Przypuszczam, że korzystanie z kontenera ma pewną zaletę, gdy masz aplikację, która korzysta z wielu interfejsów i wymaga testów jednostkowych.


1
czy możesz podać przykład? Czym różni się DI w ASP.NET od jakiegokolwiek innego typu aplikacji?
tofi9,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.