Czytam o iniekcji zależności (DI). Dla mnie jest to bardzo skomplikowana rzecz, ponieważ czytałem, że odnosi się to również do inwersji kontroli (IoC) i czułem, że będę w podróży.
Rozumiem to: zamiast tworzyć model w klasie, która również go zużywa, przekazujesz (wstrzykujesz) model (już wypełniony interesującymi właściwościami) tam, gdzie jest potrzebny (do nowej klasy, która może wziąć go jako parametr w konstruktor).
Dla mnie to tylko argument. Musiałem nie rozumieć o co chodzi? Może staje się to bardziej oczywiste przy większych projektach?
Moje rozumienie jest inne niż DI (przy użyciu pseudo kodu):
public void Start()
{
MyClass class = new MyClass();
}
...
public MyClass()
{
this.MyInterface = new MyInterface();
}
I DI byłoby
public void Start()
{
MyInterface myInterface = new MyInterface();
MyClass class = new MyClass(myInterface);
}
...
public MyClass(MyInterface myInterface)
{
this.MyInterface = myInterface;
}
Czy ktoś mógłby rzucić trochę światła, bo jestem pewien, że jestem w błocie.
dependency-injection
MyDaftQuestions
źródło
źródło
Odpowiedzi:
Tak, wstrzykujesz swoje zależności, albo przez konstruktor, albo przez właściwości.
Jednym z powodów tego jest nie obciążanie MyClass szczegółowymi informacjami na temat tego, jak należy utworzyć instancję MyInterface. MyInterface może być czymś, co samo ma całą listę zależności, a kod MyClass stałby się brzydki bardzo szybko, gdybyś utworzył instancję wszystkich zależności MyInterface w MyClass.
Innym powodem są testy.
Jeśli masz zależność od interfejsu czytnika plików i wstrzykujesz tę zależność za pomocą konstruktora, powiedzmy, ConsumerClass, co oznacza, że podczas testowania możesz przekazać implementację czytnika plików w pamięci do ConsumerClass, unikając konieczności zrobić I / O podczas testowania.
źródło
Sposób implementacji DI zależy bardzo od używanego języka.
Oto prosty przykład inny niż DI:
To jest do bani, np. Gdy do testu chcę użyć próbnego obiektu
bar
. Możemy więc uczynić to bardziej elastycznym i pozwolić na przekazywanie instancji przez konstruktor:Jest to już najprostsza forma wstrzykiwania zależności. Ale nadal jest to do bani, ponieważ wszystko musi być wykonane ręcznie (również dlatego, że osoba dzwoniąca może przechowywać odwołanie do naszych wewnętrznych obiektów i tym samym unieważniać nasz stan).
Możemy wprowadzić więcej abstrakcji, korzystając z fabryk:
Jedną z opcji jest
Foo
wygenerowanie przez abstrakcyjną fabrykęInną opcją jest przekazanie fabryki do konstruktora:
Pozostałe problemy to to, że musimy napisać nową
DependencyManager
podklasę dla każdej konfiguracji i że liczba zależności, którymi można zarządzać, jest dość ograniczona (każda nowa zależność wymaga nowej metody w interfejsie).Dzięki funkcjom takim jak odbicie i dynamiczne ładowanie klas możemy to obejść. Ale zależy to bardzo od użytego języka. W Perlu do klas można odwoływać się według ich nazw, a ja po prostu mogę
W językach takich jak Java mogę mieć menedżera zależności działającego podobnie do
Map<Class, Object>
:Do której faktycznej klasy
Bar.class
rozwiązuje się problem można skonfigurować w czasie wykonywania, np. UtrzymującMap<Class, Class>
interfejs odwzorowujący na implementacje.W pisaniu konstruktora nadal jest element manualny. Ale możemy sprawić, że konstruktor stanie się całkowicie niepotrzebny, np. Poprzez sterowanie DI za pomocą adnotacji:
Oto przykładowa implementacja małego (i bardzo ograniczonego) frameworka DI: http://ideone.com/b2ubuF , chociaż ta implementacja jest całkowicie bezużyteczna dla niezmiennych obiektów (ta naiwna implementacja nie może przyjmować żadnych parametrów dla konstruktora).
źródło
Nasi przyjaciele na Stack Overflow mają na to fajną odpowiedź . Moim ulubionym jest druga odpowiedź, w tym cytat:
z bloga Jamesa Shore'a . Oczywiście istnieją bardziej złożone wersje / wzory warstwowe, ale wystarczy po prostu zrozumieć, co się dzieje. Zamiast obiektu tworzącego własne zmienne instancji, są one przekazywane z zewnątrz.
źródło
Załóżmy, że zajmujesz się projektowaniem wnętrz w swoim salonie: instalujesz na suficie fantazyjny żyrandol i podłączasz klasyczną, dopasowaną lampę podłogową. Rok później twoja urocza żona postanawia, że wolałaby pokój nieco mniej formalny. Którą oprawę będzie łatwiej zmienić?
Ideą DI jest stosowanie podejścia „plug-in” wszędzie. Może to nie wydawać się wielkim problemem, jeśli mieszkasz w zwykłej chacie bez suchej zabudowy i odsłoniętych wszystkich przewodów elektrycznych. (Jesteś złotą rączką - możesz zmienić wszystko!) I podobnie, w małej i prostej aplikacji, DI może dodać więcej komplikacji, niż jest to warte.
Jednak w przypadku dużych i złożonych aplikacji DI jest niezbędny do długotrwałej konserwacji. Umożliwia „odłączenie” całych modułów od kodu (na przykład zaplecza bazy danych) i wymianę ich z różnymi modułami, które osiągają to samo, ale robią to w zupełnie inny sposób (na przykład system pamięci masowej w chmurze).
BTW, dyskusja na temat DI byłaby niekompletna bez rekomendacji Dependency Injection w .NET , autorstwa Marka Seemana. Jeśli znasz platformę .NET i kiedykolwiek zamierzasz uczestniczyć w tworzeniu oprogramowania SW na dużą skalę, jest to niezbędna lektura. Wyjaśnia tę koncepcję znacznie lepiej niż ja.
Pozwólcie, że zostawię wam ostatnią istotną cechę DI, którą przeoczy wasz przykład kodu. DI umożliwia wykorzystanie ogromnego potencjału elastyczności zawartego w powiedzeniu „ Programuj interfejsy ”. Aby nieznacznie zmodyfikować przykład:
Ale TERAZ, ponieważ
MyRoom
jest przeznaczony do interfejsu ILightFixture , mogę łatwo przejść w przyszłym roku i zmienić jedną linię w funkcji głównej („wstrzyknąć” inną „zależność”) i natychmiast uzyskać nową funkcjonalność w MyRoom (bez konieczności przebuduj lub ponownie wdróż MyRoom, jeśli zdarzy się, że znajduje się w osobnej bibliotece. Zakłada to oczywiście, że wszystkie implementacje ILightFixture zostały zaprojektowane z myślą o podstawieniu Liskova). Wszystko za pomocą prostej zmiany:Plus, nie mogę teraz Jednostka testowa
MyRoom
(ty jesteś testowanie jednostkowe, prawda?) I użyć „mock” lampę, która pozwala mi testowaćMyRoom
całkowicie niezależna odClassyChandelier.
Oczywiście jest wiele innych zalet, ale to one sprzedały mi ten pomysł.
źródło
Wstrzykiwanie zależności tylko przekazuje parametr, ale nadal prowadzi to do pewnych problemów, a nazwa ma na celu skupienie się na tym, dlaczego różnica między tworzeniem obiektu a przekazywaniem jest ważna.
Jest to ważne, ponieważ (oczywiście), gdy obiekt A wywoła typ B, A zależy od tego, jak działa B. Co oznacza, że jeśli A podejmie decyzję o konkretnym typie B, którego użyje, wówczas utracona zostanie duża elastyczność w tym, w jaki sposób A może być używane przez klasy zewnętrzne, bez modyfikacji A.
Wspominam o tym, ponieważ mówisz o „pomijaniu sedna” zastrzyku uzależnienia, i właśnie dlatego ludzie lubią to robić. Może to po prostu oznaczać przekazanie parametru, ale to, czy to zrobisz, może być ważne.
Ponadto niektóre trudności z faktycznym wdrażaniem technologii DI lepiej pokazują się w dużych projektach. Na ogół przeniesiesz faktyczną decyzję, które konkretne klasy zastosować do samego końca, aby Twoja metoda początkowa mogła wyglądać następująco:
ale z kolejnymi 400 liniami tego rodzaju nitującego kodu. Jeśli wyobrażasz sobie utrzymanie tego z czasem, atrakcyjność kontenerów DI staje się bardziej widoczna.
Wyobraź sobie także klasę, która, powiedzmy, implementuje IDisposable. Jeśli klasy, które go używają, pobierają go poprzez wstrzyknięcie, a nie tworzą go same, to skąd wiedzą, kiedy wywołać Dispose ()? (Który to kolejny problem, którym zajmuje się kontener DI).
Tak, oczywiście, wstrzykiwanie zależności jest po prostu przekazywaniem parametru, ale tak naprawdę, gdy ludzie mówią, że wstrzykiwanie zależności oznacza „przekazywanie parametru do obiektu w porównaniu z alternatywą polegającą na tym, że obiekt sam tworzy instancję czegoś samego”, i radzenie sobie ze wszystkimi zaletami wad takie podejście, być może przy pomocy kontenera DI.
źródło
Na poziomie klasy jest to łatwe.
„Dependency Injection” po prostu odpowiada na pytanie „jak znajdę moich współpracowników” za pomocą „oni są na ciebie naciskani - nie musisz iść i sam ich zdobyć”. (Jest podobny - ale nie taki sam jak - „Inwersja kontroli”, gdzie na pytanie „w jaki sposób mam zamówić operacje na podstawie moich danych wejściowych?” Ma podobną odpowiedź).
Tylko korzyść, że posiadanie współpracownicy popchnął na ciebie jest to, że umożliwia kodu klienckiego do korzystania z klasy do komponowania wykres obiektu, który odpowiada swoją obecną potrzebę ... nie ma arbitralnie z góry określony kształt i zmienność na wykresie przez prywatnie decydując konkretne typy i cykl życia współpracowników.
(Wszystkie te inne korzyści, testowalność, luźne sprzężenie itp. Wynikają w dużej mierze z zastosowania interfejsów, a nie tyle z zależności wstrzykiwania zależności, chociaż DI naturalnie promuje stosowanie interfejsów).
Warto zauważyć, że jeśli unikniesz tworzenia własnych współpracowników, twoja klasa musi zatem uzyskać współpracowników od konstruktora, właściwości lub argumentu metody (ta druga opcja jest często pomijana, nawiasem mówiąc ... robi to nie zawsze ma sens, aby „kolaboranci klasy” byli częścią swojego „stanu”).
I to dobrze.
Na poziomie aplikacji ...
Tyle o widoku rzeczy dla poszczególnych klas. Załóżmy, że masz wiele klas, które działają zgodnie z zasadą „nie twórz własnych współpracowników” i chcesz złożyć z nich aplikację. Najprostszą rzeczą do zrobienia jest użycie starego dobrego kodu (bardzo przydatnego narzędzia do wywoływania konstruktorów, właściwości i metod!) Do skomponowania pożądanego wykresu obiektowego i dołożenia do niego pewnych danych wejściowych. (Tak, niektóre z tych obiektów na wykresie same w sobie będą obiektami-fabrykami, które zostały przekazane jako współpracownicy innym długowiecznym obiektom na wykresie, gotowym do obsługi ... nie można wstępnie zbudować każdego obiektu! ).
... potrzeba „elastycznego” skonfigurowania wykresu obiektowego aplikacji ...
W zależności od innych (niezwiązanych z kodem) celów możesz chcieć dać użytkownikowi końcowemu kontrolę nad wdrożonym w ten sposób wykresem obiektowym. To prowadzi cię w kierunku schematu konfiguracji, niezależnie od tego, czy jest to plik tekstowy własnego projektu z pewnymi parami nazwa / wartość, plik XML, niestandardowy DSL, deklaratywny język opisu graficznego, taki jak YAML, imperatywny język skryptowy, taki jak JavaScript, lub coś innego odpowiedniego do wykonywanego zadania. Wszystko, czego potrzeba, aby skomponować prawidłowy wykres obiektowy, w sposób odpowiadający potrzebom użytkowników.
... może być znaczącą siłą projektową.
W najbardziej skrajnej sytuacji tego typu, można zdecydować się trwać bardzo ogólne podejście i dać użytkownikom końcowym ogólny mechanizm „okablowanie up” z obiektu-wykres ich wyboru, a nawet pozwolić im , aby zapewnić konkretne realizacje interfejsów do środowisko uruchomieniowe! (Twoja dokumentacja jest lśniącym klejnotem, użytkownicy są bardzo inteligentni, znają co najmniej gruboziarnisty zarys grafu obiektowego aplikacji, ale nie mają przydatnego kompilatora). Ten scenariusz może teoretycznie wystąpić w niektórych sytuacjach „korporacyjnych”.
W takim przypadku prawdopodobnie masz deklaratywny język, który pozwala użytkownikom wyrażać dowolny typ, kompozycję grafu obiektowego takich typów oraz paletę interfejsów, które mityczny użytkownik końcowy może mieszać i dopasowywać. Aby zmniejszyć obciążenie poznawcze użytkowników, wolisz podejście „konfiguracja zgodnie z konwencją”, tak aby musieli tylko wkroczyć i przejechać interesujący fragment wykresu obiektowego, zamiast zmagać się z całością.
Ty biedny darni!
Ponieważ nie masz ochoty pisać tego wszystkiego samemu (ale poważnie, sprawdź powiązanie YAML dla swojego języka), używasz pewnego rodzaju frameworku DI.
W zależności od dojrzałości tego frameworka, możesz nie mieć opcji użycia wstrzykiwania przez konstruktora, nawet jeśli ma to sens (kolaboranci nie zmieniają się przez cały czas istnienia obiektu), zmuszając w ten sposób do użycia Settera Injection (nawet gdy współpracownicy nie zmieniają się przez cały czas istnienia obiektu, a nawet jeśli nie ma tak naprawdę logicznego powodu, dla którego wszystkie konkretne implementacje interfejsu muszą mieć współpracowników określonego typu). Jeśli tak, to obecnie znajdujesz się w piekle silnie sprzężonym, pomimo starannego „wykorzystywania interfejsów” w całej bazie kodu - horror!
Mamy jednak nadzieję, że użyłeś frameworka DI, który daje ci możliwość wstrzyknięcia konstruktora, a twoi użytkownicy są trochę zrzędliwi, że nie spędzasz więcej czasu na zastanawianiu się nad konkretnymi rzeczami, których potrzebowali do skonfigurowania i nadaniu im interfejsu bardziej odpowiedniego do zadania na dłoni. (Chociaż, żeby być uczciwym, prawdopodobnie próbowałeś wymyślić jakiś sposób, ale JavaEE Cię zawiodło i musiałeś uciekać się do tego okropnego włamania).
Bootnote
Nigdy nie używasz Google Guice, co daje koderowi sposób na zrezygnowanie z tworzenia wykresu obiektowego z kodem ... przez pisanie kodu. Argh!
źródło
Wiele osób mówi tutaj, że DI jest mechanizmem outsourcingu inicjalizacji zmiennych instancji.
Dla oczywistych i prostych przykładów tak naprawdę nie potrzebujesz „wstrzykiwania zależności”. Możesz to zrobić za pomocą prostego parametru przekazanego do konstruktora, jak zauważyłeś w pytaniu.
Myślę jednak, że dedykowany mechanizm „wstrzykiwania zależności” jest przydatny, gdy mówimy o zasobach na poziomie aplikacji, w których zarówno osoba dzwoniąca, jak i osoba odbierająca nic nie wie o tych zasobach (i nie chce wiedzieć!).
Na przykład: połączenie z bazą danych, kontekst bezpieczeństwa, funkcja rejestrowania, plik konfiguracyjny itp.
Ponadto, ogólnie rzecz biorąc, każdy singleton jest dobrym kandydatem na DI. Im więcej masz i używasz, tym większe szanse na DI.
W przypadku tego rodzaju zasobów jest całkiem jasne, że nie ma sensu przenosić ich dookoła za pomocą list parametrów, a zatem wynaleziono DI.
Ostatnia uwaga, potrzebujesz również pojemnika DI, który wykona rzeczywisty zastrzyk ... (ponownie, aby zwolnić zarówno dzwoniącego, jak i odbierającego ze szczegółów implementacji).
źródło
Jeśli przekażesz abstrakcyjny typ odwołania, kod wywołujący może przekazać dowolny obiekt, który rozszerza / implementuje typ abstrakcyjny. Tak więc w przyszłości klasa, która korzysta z tego obiektu, mogłaby użyć nowego obiektu, który został utworzony bez modyfikowania kodu.
źródło
To kolejna opcja oprócz odpowiedzi Amona
Użyj konstruktorów:
Tego używam do zależności. Nie używam żadnego środowiska DI. Oni przeszkadzają.
źródło