Rozumiem, że bezpośrednie tworzenie instancji zależności w klasie jest uważane za złą praktykę. Ma to sens, ponieważ tak ścisłe łączenie wszystkiego, co z kolei sprawia, że testowanie jest bardzo trudne.
Prawie wszystkie frameworki, które spotkałem, wydają się faworyzować wstrzykiwanie zależności za pomocą kontenera niż używanie lokalizatorów usług. Oba wydają się osiągać to samo, pozwalając programiście na określenie, który obiekt powinien zostać zwrócony, gdy klasa wymaga zależności.
Jaka jest różnica między nimi? Dlaczego miałbym wybierać jeden nad drugim?
dependency-injection
ioc-containers
service-locator
tom6025222
źródło
źródło
Odpowiedzi:
Kiedy sam obiekt jest odpowiedzialny za żądanie swoich zależności, w przeciwieństwie do akceptowania ich przez konstruktor, ukrywa pewne istotne informacje. Jest tylko nieznacznie lepszy niż bardzo ściśle powiązany przypadek użycia
new
do utworzenia jego zależności. Zmniejsza to sprzężenie, ponieważ w rzeczywistości można zmienić zależności, które się dostaje, ale nadal istnieje zależność, której nie można wstrząsnąć: lokalizator usług. Staje się to tym, od czego wszystko zależy.Kontener dostarczający zależności za pomocą argumentów konstruktora zapewnia największą przejrzystość. Widzimy od razu, że obiekt potrzebuje zarówno an
AccountRepository
, jak i aPasswordStrengthEvaluator
. Podczas korzystania z lokalizatora usług informacje te są mniej widoczne od razu. Od razu zobaczysz przypadek, w którym obiekt ma 17 zależności, i powiesz sobie: „Hmm, to wydaje się dużo. Co się tam dzieje?” Połączenia z lokalizatorem usług mogą być rozłożone na różne metody i kryć się za logiką warunkową, i możesz nie zdawać sobie sprawy, że stworzyłeś „klasę Boga” - taką, która robi wszystko. Być może ta klasa mogłaby zostać przekształcona w 3 mniejsze klasy, które są bardziej skoncentrowane, a zatem bardziej testowalne.Teraz rozważ testowanie. Jeśli obiekt korzysta z lokalizatora usług w celu uzyskania swoich zależności, środowisko testowe będzie również potrzebowało lokalizatora usług. W teście skonfigurujesz lokalizator usług, aby dostarczał zależności do testowanego obiektu - może a
FakeAccountRepository
i aVeryForgivingPasswordStrengthEvaluator
, a następnie uruchom test. Ale to więcej pracy niż określenie zależności w konstruktorze obiektu. Twoja struktura testowa staje się również zależna od lokalizatora usług. Jest to kolejna rzecz, którą musisz skonfigurować w każdym teście, co sprawia, że pisanie testów jest mniej atrakcyjne.Poszukaj „Serivce Locator to antywzór” dla artykułu Marka Seemana na ten temat. Jeśli jesteś w świecie .Net, zdobądź jego książkę. To jest bardzo dobre.
źródło
constructor supplied dependencies
vs,service locator
jest to, że pierwszą można zweryfikować w czasie kompilacji, a drugą można zweryfikować tylko w czasie wykonywania.But that's more work than specifying dependencies in the object's constructor.
chciałbym się sprzeciwić. W przypadku lokalizatora usług wystarczy tylko określić 3 zależności, które są rzeczywiście potrzebne do testu. W przypadku DI opartego na konstruktorze musisz podać WSZYSTKIE 10 z nich, nawet jeśli 7 jest nieużywanych.Wyobraź sobie, że jesteś pracownikiem fabryki produkującej buty .
Jesteś odpowiedzialny za składanie butów, więc będziesz potrzebować wielu rzeczy, aby to zrobić.
I tak dalej.
Jesteś w pracy w fabryce i możesz zacząć. Masz listę instrukcji, jak postępować, ale nie masz jeszcze żadnych materiałów ani narzędzi.
Lokalizator usług jest jak brygadzista, który pomoże ci zdobyć to, czego potrzebujesz.
Pytasz Lokalizator usług za każdym razem, gdy czegoś potrzebujesz, a oni idą znaleźć to dla Ciebie. Lokalizator usług został wcześniej poinformowany o tym, o co prawdopodobnie poprosisz i jak go znaleźć.
Miej jednak nadzieję, że nie poprosisz o coś nieoczekiwanego. Jeśli Lokalizator nie został wcześniej poinformowany o określonym narzędziu lub materiale, nie będzie w stanie go dla ciebie zdobyć i po prostu wzrusza ramionami.
Pojemnik do wstrzykiwania zależności (DI) jest jak duże pudełko, które wypełnia się wszystkim, czego wszyscy potrzebują na początku dnia.
Gdy fabryka zostaje uruchomiona, Wielki Szef znany jako Korzeń Kompozycji chwyta pojemnik i rozdaje wszystko Kierownikom Linii .
Kierownicy liniowi mają teraz to, czego potrzebują, aby wykonywać swoje obowiązki na dany dzień. Biorą to, co mają i przekazują to, co jest potrzebne, swoim podwładnym.
Proces ten trwa, a zależności spadają na linię produkcyjną. Ostatecznie dla majstra pojawia się pojemnik z materiałami i narzędziami.
Twój brygadzista rozdaje teraz dokładnie to, czego potrzebujesz tobie i innym pracownikom, nawet o to nie pytając.
Zasadniczo, jak tylko pojawi się w pracy, wszystko, czego potrzebujesz, jest już w pudełku i czeka na Ciebie. Nie musiałeś nic wiedzieć o tym, jak je zdobyć.
źródło
Kilka dodatkowych punktów, które znalazłem podczas przeszukiwania sieci:
źródło
Spóźniam się na to przyjęcie, ale nie mogę się oprzeć.
Czasami wcale. Różnica polega na tym, co wie o czym.
Wiesz, że używasz lokalizatora usług, gdy klient szukający zależności wie o kontenerze. Klient umiejący znaleźć swoje zależności, nawet przechodząc przez kontener w celu ich uzyskania, jest wzorcem lokalizatora usług.
Czy to oznacza, że jeśli chcesz uniknąć lokalizatora usług, nie możesz użyć kontenera? Nie. Musisz tylko powstrzymać klientów przed wiedzą o kontenerze. Kluczową różnicą jest to, gdzie używasz kontenera.
Pozwala powiedzieć
Client
potrzebyDependency
. Kontener maDependency
.Właśnie zastosowaliśmy się do wzorca lokalizatora usług, ponieważ
Client
wie, jak znaleźćDependency
. Jasne, że używa twardego kodu,ClassPathXmlApplicationContext
ale nawet jeśli wstrzykujesz, że nadal masz lokalizator usług, ponieważClient
połączeniabeanfactory.getBean()
.Aby uniknąć lokalizatora usług, nie musisz porzucać tego kontenera. Musisz go po prostu usunąć,
Client
więcClient
nie wiem o tym.Zauważ, że
Client
teraz nie ma pojęcia, że kontener istnieje:Przenieś pojemnik ze wszystkich klientów i umieść go w głównym miejscu, w którym może zbudować wykres obiektów wszystkich twoich długo żyjących obiektów. Wybierz jeden z tych obiektów do wyodrębnienia i wywołaj na nim metodę, a ty zaczniesz zaznaczanie całego wykresu.
To przenosi całą statyczną konstrukcję do XML-a kontenerów, a jednocześnie sprawia, że wszyscy klienci są błogo nieświadomi, jak znaleźć ich zależności.
Ale główny nadal wie, jak zlokalizować zależności! Tak. Ale nie rozpowszechniając tej wiedzy, uniknąłeś podstawowego problemu lokalizatora usług. Decyzja o użyciu kontenera jest teraz podejmowana w jednym miejscu i można ją zmienić bez przepisywania setek klientów.
źródło
Myślę, że najłatwiejszym sposobem na zrozumienie różnicy między tymi dwoma i dlaczego kontener DI jest o wiele lepszy niż lokalizator usług, to zastanowienie się, dlaczego w pierwszej kolejności dokonujemy inwersji zależności.
Dokonujemy inwersji zależności, aby każda klasa wyraźnie określała dokładnie, od czego zależy działanie. Robimy to, ponieważ tworzy to najsłabsze sprzęgło, jakie możemy osiągnąć. Im luźniejsze sprzężenie, tym łatwiej coś przetestować i zrefaktoryzować (i generalnie wymaga to najmniej refaktoryzacji w przyszłości, ponieważ kod jest czystszy).
Spójrzmy na następującą klasę:
W tej klasie wyraźnie stwierdzamy, że potrzebujemy IOutputProvider i nic więcej, aby ta klasa działała. Jest to w pełni testowalne i zależy od jednego interfejsu. Mogę przenieść tę klasę do dowolnego miejsca w mojej aplikacji, w tym do innego projektu, a wszystko czego potrzebuje to dostęp do interfejsu IOutputProvider. Jeśli inni programiści chcą dodać coś nowego do tej klasy, co wymaga drugiej zależności, muszą wyraźnie powiedzieć, czego potrzebują w konstruktorze.
Spójrz na tę samą klasę z lokalizatorem usług:
Teraz dodałem lokalizator usług jako zależność. Oto problemy, które są od razu oczywiste:
Dlaczego więc nie uczynić lokalizatora usług klasą statyczną? Spójrzmy:
To jest o wiele prostsze, prawda?
Źle.
Powiedzmy, że IOutputProvider jest implementowany przez bardzo długo działającą usługę internetową, która zapisuje ciąg znaków w piętnastu różnych bazach danych na całym świecie i zajmuje bardzo dużo czasu.
Spróbujmy przetestować tę klasę. Do testu potrzebujemy innej implementacji IOutputProvider. Jak piszemy test?
Aby to zrobić, musimy wykonać fantazyjną konfigurację w statycznej klasie ServiceLocator, aby użyć innej implementacji IOutputProvider, gdy jest ona wywoływana przez test. Nawet napisanie tego zdania było bolesne. Wdrożenie tego byłoby torturujące i byłoby koszmarem konserwacyjnym . Nigdy nie powinniśmy musieć modyfikować klasy specjalnie do testowania, szczególnie jeśli ta klasa nie jest klasą, którą faktycznie próbujemy przetestować.
Pozostaje Ci albo a) test, który powoduje natrętne zmiany kodu w niepowiązanej klasie ServiceLocator; lub b) brak testu. I pozostaje Ci również mniej elastyczne rozwiązanie.
Zatem klasa lokalizatora usług musi zostać wstrzyknięta do konstruktora. Co oznacza, że pozostały nam konkretne problemy wspomniane wcześniej. Lokalizator usług wymaga więcej kodu, mówi innym programistom, że potrzebuje rzeczy, których nie potrzebuje, zachęca innych programistów do pisania gorszego kodu i daje nam mniejszą elastyczność w posuwaniu się naprzód.
Mówiąc prosto , lokalizatory usług zwiększają sprzężenie w aplikacji i zachęcają innych programistów do pisania wysoce sprzężonego kodu .
źródło