Trudno mi znaleźć zasoby na temat tego, dlaczego powinienem używać zastrzyku zależności . Większość zasobów, które widzę, wyjaśnia, że po prostu przekazuje instancję obiektu do innej instancji obiektu, ale dlaczego? Czy to tylko dla czystszej architektury / kodu czy wpływa to na wydajność jako całość?
Dlaczego powinienem wykonać następujące czynności?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Zamiast tego?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
deactivateProfile
Sugeruje mi jednak, że ustawienie wartościisActive
false bez dbania o jej poprzedni stan jest tutaj właściwym podejściem. Wywołanie metody z natury oznacza, że masz zamiar ustawić ją jako nieaktywną, a nie uzyskać jej aktualny (nieaktywny) status.Odpowiedzi:
Zaletą jest to, że bez wstrzykiwania zależności, twoja klasa Profile
Ale z zastrzykiem zależności
Może to wydawać się (lub nawet być) nieistotne w tym konkretnym przypadku, ale wyobraź sobie, że nie mówimy o obiekcie Settings, ale obiekcie DataStore, który może mieć różne implementacje, takim, który przechowuje dane w plikach, a innym, który przechowuje je w baza danych. A w przypadku testów automatycznych potrzebujesz także próbnej implementacji. Teraz naprawdę nie chcesz, aby klasa Profile zapisywała kod, którego używa - a co ważniejsze, naprawdę nie chcesz, aby klasa Profile wiedziała o ścieżkach systemu plików, połączeniach DB i hasłach, więc tworzenie obiektów DataStore musi się zdarzyć gdzie indziej.
źródło
This may seem (or even be) irrelevant in this particular case
Myślę, że w rzeczywistości jest to bardzo istotne. Jak uzyskasz ustawienia? Wiele systemów, które widziałem, będzie miało zakodowany domyślny zestaw ustawień i publiczną konfigurację, więc musisz załadować oba i zastąpić niektóre wartości ustawieniami publicznymi. Możesz nawet potrzebować wielu źródeł domyślnych. Być może dostajesz niektóre z dysku, inne z DB. Tak więc cała logika nawet uzyskiwania ustawień może i jest często nietrywialna - na pewno nie powinien lub nie powinien dbać o coś pochłaniającego kod.$setting = new Setting();
strasznie nieefektywna. Tworzenie instancji wtrysku i obiektu odbywa się raz.Wstrzykiwanie zależności ułatwia testowanie kodu.
Nauczyłem się tego z pierwszej ręki, kiedy miałem za zadanie naprawić trudny do złapania błąd w integracji Magento z PayPal.
Pojawiłby się problem, gdy PayPal informował Magento o nieudanej płatności: Magento nie zarejestrował prawidłowo nieprawidłowości.
Ręczne testowanie potencjalnej poprawki byłoby bardzo żmudne: trzeba by w jakiś sposób uruchomić powiadomienie PayPal „Nieudane”. Musisz przesłać e-czek, anulować go i poczekać, aż błąd się skończy. Oznacza to ponad 3 dni na przetestowanie jednoznakowej zmiany kodu!
Na szczęście wydaje się, że twórcy rdzenia Magento, którzy opracowali tę funkcję, mieli na myśli testy i zastosowali wzorzec wstrzykiwania zależności, aby uczynić ją trywialną. To pozwala nam zweryfikować naszą pracę za pomocą prostego przypadku testowego takiego jak ten:
Jestem pewien, że wzorzec DI ma wiele innych zalet, ale zwiększona testowalność jest jedną z największych korzyści w mojej głowie .
Jeśli jesteś ciekawy rozwiązania tego problemu, sprawdź repozytorium GitHub tutaj: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
źródło
Dlaczego (o co w tym chodzi)?
Najlepszym mnemonikiem, jaki znalazłem do tego, jest „ nowy klej ”: za każdym razem, gdy używasz
new
swojego kodu, kod ten jest powiązany z tą konkretną implementacją . Jeśli wielokrotnie użyjesz nowego w konstruktorach, utworzysz łańcuch konkretnych implementacji . A ponieważ nie można „mieć” instancji klasy bez jej zbudowania, nie można rozdzielić tego łańcucha.Jako przykład wyobraź sobie, że piszesz grę wideo na samochód wyścigowy. Zacząłeś od klasy
Game
, która tworzy aRaceTrack
, która tworzy 8Cars
, z których każda tworzy aMotor
. Teraz, jeśli chcesz 4 dodatkoweCars
z innym przyspieszeniem, będziesz musiał zmienić każdą wymienioną klasę , z wyjątkiem możeGame
.Kod czyszczący
Tak .
Jednak w tej sytuacji może to wydawać się mniej jasne , ponieważ jest to bardziej przykład tego, jak to zrobić . Faktyczna przewaga pokazuje się tylko wtedy, gdy zaangażowanych jest kilka klas i trudniej jest ją zademonstrować, ale wyobraź sobie, że użyłbyś DI w poprzednim przykładzie. Kod tworzący te wszystkie rzeczy może wyglądać mniej więcej tak:
Dodanie tych 4 różnych samochodów można teraz zrobić, dodając następujące wiersze:
RaceTrack
,Game
,Car
, lubMotor
były konieczne - co oznacza, że możemy być w 100% pewien, że nie wprowadzi jakieś nowe błędy!Uwagi dotyczące wydajności
Nie . Ale szczerze mówiąc, może tak być.
Jednak nawet w takim przypadku jest to tak absurdalnie mała ilość, że nie musisz się tym przejmować. Jeśli w pewnym momencie w przyszłości, trzeba napisać kod dla Tamagotchi z równowartości 5 MHz CPU i 2MB pamięci RAM, to może ty może trzeba dbać o to.
W 99,999% * przypadków będzie miało lepszą wydajność, ponieważ spędzasz mniej czasu na naprawianiu błędów i więcej czasu na ulepszaniu algorytmów obciążających zasoby.
* całkowicie skompletowany numer
Dodano informacje: „na stałe”
Nie popełnijcie błędu, jest to nadal bardzo „zakodowane na stałe” - liczby są zapisywane bezpośrednio w kodzie. Nie zakodowane na stałe oznaczałoby coś w rodzaju przechowywania tych wartości w pliku tekstowym - np. W formacie JSON - a następnie odczytania ich z tego pliku.
W tym celu należy dodać kod do odczytu pliku, a następnie parsowania JSON. Jeśli ponownie rozważysz przykład; w wersji innej niż DI, a
Car
lub aMotor
teraz musi odczytać plik. To nie brzmi tak, jakby miało to zbyt duży sens.W wersji DI dodajesz go do kodu konfigurującego grę.
źródło
Zawsze byłem zaskoczony zastrzykiem zależności. Wydawało się, że istnieje tylko w sferach Jawy, ale te sfery mówiły o tym z wielką czcią. To był jeden z wielkich Wzorów, o których mówi się, że wprowadzają porządek w chaos. Ale przykłady zawsze były skomplikowane i sztuczne, ustanawiając problem, a następnie podejmując się jego rozwiązania, czyniąc kod bardziej skomplikowanym.
Bardziej sensowne było, gdy inny twórca Pythona przekazał mi tę mądrość: po prostu przekazuje argumenty do funkcji. To prawie nie jest wzór; bardziej przypomina przypomnienie, że możesz poprosić o coś jako argument, nawet jeśli sam mógłbyś sobie wyobrazić rozsądną wartość.
Więc twoje pytanie jest mniej więcej równoważne z „dlaczego moja funkcja powinna brać argumenty?” i ma wiele takich samych odpowiedzi, a mianowicie: pozwolić dzwoniącemu na podejmowanie decyzji .
Oczywiście wiąże się to z pewnymi kosztami, ponieważ teraz zmuszasz osobę dzwoniącą do podjęcia pewnej decyzji (chyba że podasz argument jako opcjonalny), a interfejs jest nieco bardziej złożony. W zamian zyskujesz elastyczność.
Więc: Czy jest jakiś dobry powód, dla którego musisz konkretnie użyć tego konkretnego
Setting
typu / wartości? Czy istnieje dobry powód, aby wywoływać kod, który może chcieć innegoSetting
typu / wartości? (Pamiętaj, testy są kodem !)źródło
Podany przykład nie jest wstrzykiwaniem zależności w sensie klasycznym. Wstrzykiwanie zależności zwykle odnosi się do przekazywania obiektów w konstruktorze lub przy użyciu „wstrzykiwania ustawiacza” tuż po utworzeniu obiektu, aby ustawić wartość w polu w nowo utworzonym obiekcie.
Twój przykład przekazuje obiekt jako argument do metody instancji. Ta metoda instancji następnie modyfikuje pole tego obiektu. Zastrzyk zależności? Nie. Zerwanie enkapsulacji i ukrywanie danych? Absolutnie!
Teraz, jeśli kod był taki:
Powiedziałbym, że używasz zastrzyku zależności. Istotną różnicą jest
Settings
obiekt przekazywany do konstruktora lub aProfile
obiektu.Jest to przydatne, jeśli obiekt Settings jest drogi lub skomplikowany w budowie, lub Settings to interfejs lub klasa abstrakcyjna, w której istnieje wiele konkretnych implementacji w celu zmiany zachowania w czasie wykonywania.
Ponieważ bezpośrednio uzyskujesz dostęp do pola w obiekcie Ustawienia, a nie wywołujesz metodę, nie możesz skorzystać z polimorfizmu, który jest jedną z zalet wstrzykiwania zależności.
Wygląda na to, że ustawienia profilu są specyficzne dla tego profilu. W takim przypadku zrobiłbym jedną z następujących czynności:
Utwórz instancję obiektu Settings wewnątrz konstruktora profilu
Przekaż obiekt Ustawienia w konstruktorze i skopiuj poszczególne pola, które dotyczą profilu
Szczerze mówiąc, przekazanie obiektu Settings do,
deactivateProfile
a następnie zmodyfikowanie wewnętrznego pola obiektu Settings to zapach kodu. Obiekt Settings powinien być jedynym, który modyfikuje swoje pola wewnętrzne.źródło
The example you give is not dependency injection in the classical sense.
- To nie ma znaczenia. Ludzie z OO mają przedmioty w mózgu, ale nadal uzależniasz się od czegoś.Wiem, że spóźniam się na to przyjęcie, ale wydaje mi się, że brakuje ważnego punktu.
Nie powinieneś Ale nie dlatego, że wstrzykiwanie zależności jest złym pomysłem. To dlatego, że robi to źle.
Spójrzmy na te rzeczy za pomocą kodu. Zrobimy to:
kiedy wyciągniemy z tego to samo:
Wydaje się to oczywiście stratą czasu. To kiedy robisz to w ten sposób. To nie jest najlepsze zastosowanie wstrzykiwania zależności. To nawet nie najlepsze wykorzystanie klasy.
Co jeśli zamiast tego mielibyśmy to:
A teraz
application
można go aktywować i dezaktywowaćprofile
bez konieczności posiadania szczególnej wiedzy na temat tegosetting
, co się zmienia. Czemu to jest dobre? W razie potrzeby zmiany ustawienia. Theapplication
Jest murem off z tych zmian tak, jesteś wolny, aby przejść orzechy w bezpiecznej przestrzeni zawartej bez przerwy oglądać wszystko jak najszybciej dotknąć czegoś.Wynika to z odrębnej konstrukcji od zachowania zasady . Wzór DI jest tutaj prosty. Zbuduj wszystko, czego potrzebujesz, na jak najniższym poziomie, połącz je ze sobą, a następnie rozpocznij od tykania wszystkich zachowań.
W rezultacie masz oddzielne miejsce do decydowania o tym, co łączy się z czym, oraz inne miejsce do zarządzania tym, co mówi co do czego.
Spróbuj tego na czymś, co musisz utrzymywać w czasie i sprawdź, czy to nie pomaga.
źródło
Czy jako klient, kiedy zatrudniasz mechanika, który zrobi coś z twoim samochodem, oczekujesz, że ten mechanik zbuduje samochód od zera, aby potem z nim pracować? Nie, dajesz mechanikowi samochód, nad którym chcesz, aby pracował .
Jako właściciel garażu, kiedy instruujesz mechanika, aby zrobił coś z samochodem, czy spodziewasz się, że mechanik stworzy własny śrubokręt / klucz / części samochodowe? Nie, dostarczasz mechanikowi części / narzędzia, których potrzebuje
Dlaczego to robimy? Cóż Pomyśl o tym. Jesteś właścicielem garażu, który chce zatrudnić kogoś, kto zostanie twoim mechanikiem. Nauczysz ich mechaniki (= napiszesz kod).
Co będzie łatwiejsze:
Ogromne korzyści sprawiają, że mechanik nie tworzy wszystkiego od zera:
Jeśli zatrudnisz i wyszkolisz mechanika-eksperta, skończysz z pracownikiem, który kosztuje więcej, poświęca więcej czasu na wykonanie czegoś, co powinno być prostą pracą, i zawsze będzie musiał być przekwalifikowany za każdym razem, gdy jeden z wielu obowiązków tego wymaga do zaktualizowania.
Analogia programistyczna jest taka, że jeśli użyjesz klas z zakodowanymi zależnościami, to skończysz z trudnymi do utrzymania klasami, które będą wymagały ciągłej przebudowy / zmian za każdym razem, gdy
Settings
tworzona jest nowa wersja obiektu ( w twoim przypadku), a ty Będę musiał rozwinąć wewnętrzną logikę, aby klasa mogła tworzyć różne typySettings
obiektów.Ponadto, kto spożywa twoją klasę, będzie musiał teraz poprosić klasę o utworzenie poprawnego
Settings
obiektu, w przeciwieństwie do zwykłego przekazania klasie dowolnegoSettings
obiektu, który chce przekazać. Oznacza to dodatkowy rozwój dla konsumenta, aby dowiedzieć się, jak poprosić klasę o stworzenie odpowiedniego narzędzia.Tak, inwersja zależności wymaga nieco więcej wysiłku, niż zapisanie zależności na stałe. Tak, denerwujące jest pisanie więcej.
Jest to jednak ten sam argument, co wybór literału wartości kodu, ponieważ „deklarowanie zmiennych wymaga więcej wysiłku”. Technicznie poprawne, ale pro przewyższają wady o kilka rzędów wielkości .
Korzyści wynikające z odwrócenia zależności nie są odczuwalne podczas tworzenia pierwszej wersji aplikacji. Korzyści z inwersji zależności są odczuwalne, gdy trzeba zmienić lub rozszerzyć tę początkową wersję. I nie oszukuj się, myśląc, że za pierwszym razem dobrze to zrobisz i nie będziesz musiał przedłużać / zmieniać kodu. Będziesz musiał coś zmienić.
Nie wpływa to na wydajność środowiska wykonawczego aplikacji. Ale ma to ogromny wpływ na czas programowania (a tym samym wydajność) programisty .
źródło
Jak wszystkie wzory, bardzo ważne jest pytanie „dlaczego”, aby uniknąć rozdętych wzorów.
W przypadku wstrzykiwania zależności można to łatwo dostrzec, myśląc o dwóch, prawdopodobnie najważniejszych aspektach projektu OOP ...
Niskie sprzęgło
Sprzężenie w programowaniu komputerowym :
Chcesz osiągnąć niskie sprzężenie. Dwie rzeczy silnie powiązane oznaczają, że jeśli zmienisz jedną, najprawdopodobniej będziesz musiał zmienić drugą. Błędy lub ograniczenia w jednym prawdopodobnie spowodują błędy / ograniczenia w drugim; i tak dalej.
Tworzenie obiektów jednej klasy przez inne jest bardzo silnym sprzężeniem, ponieważ jeden musi wiedzieć o istnieniu drugiego; musi wiedzieć, jak go utworzyć (jakich argumentów potrzebuje konstruktor), a te argumenty muszą być dostępne podczas wywoływania konstruktora. Ponadto, w zależności od tego, czy język wymaga wyraźnej dekonstrukcji (C ++), wprowadzi to dalsze komplikacje. Jeśli wprowadzisz nowe klasy (tj. A
NextSettings
lub cokolwiek), musisz wrócić do oryginalnej klasy i dodać więcej wywołań do ich konstruktorów.Wysoka spójność
Spójność :
To jest druga strona medalu. Jeśli spojrzysz na jedną jednostkę kodu (jedną metodę, jedną klasę, jedną paczkę itp.), Chcesz mieć cały kod wewnątrz tej jednostki, aby mieć jak najmniej obowiązków.
Podstawowym przykładem może być wzorzec MVC: wyraźnie oddzielasz model domeny od widoku (GUI) i warstwę kontrolną, która skleja je ze sobą.
Pozwala to uniknąć nadmiaru kodu w przypadku dużych fragmentów, które wykonują wiele różnych rzeczy; jeśli chcesz zmienić jakąś jego część, musisz również śledzić wszystkie inne funkcje, starając się unikać błędów itp .; i szybko zaprogramujesz się w dziurze, w której bardzo trudno się wydostać.
Dzięki wstrzykiwaniu zależności delegujesz tworzenie lub śledzenie zależności, cóż, do dowolnych klas (lub plików konfiguracyjnych), które implementują twoją DI. Inne klasy nie będą się przejmować tym, co się właściwie dzieje - będą pracować z niektórymi rodzajowymi interfejsami i nie mają pojęcia, co to jest rzeczywista implementacja, co oznacza, że nie są odpowiedzialne za inne rzeczy.
źródło
Powinieneś użyć technik, aby rozwiązać problemy, które są dobre w rozwiązywaniu problemów. Odwrócenie zależności i wtrysk nie różnią się.
Inwersja lub wstrzykiwanie zależności jest techniką, która pozwala Twojemu kodowi decydować, która implementacja metody zostanie wywołana w czasie wykonywania. Maksymalizuje to zalety późnego wiązania. Technika jest konieczna, gdy język nie obsługuje zastępowania w czasie wykonywania funkcji innych niż instancja. Na przykład Java nie ma mechanizmu zastępującego wywołania metody statycznej wywołaniami innej implementacji; w przeciwieństwie do Pythona, gdzie wszystko, co jest konieczne do zastąpienia wywołania funkcji, to powiązanie nazwy z inną funkcją (ponowne przypisanie zmiennej przechowującej funkcję).
Dlaczego chcielibyśmy różnicować implementację funkcji? Istnieją dwa główne powody:
Możesz także wziąć pod uwagę odwrócenie kontenerów kontrolnych. Jest to technika, która ma na celu pomóc ci uniknąć dużych, splątanych drzew konstrukcyjnych, które wyglądają jak ten pseudokod:
Pozwala zarejestrować zajęcia, a następnie wykonuje dla Ciebie konstrukcję:
Zauważ, że najłatwiej jest, jeśli zarejestrowane klasy mogą być bezpaństwowymi singletonami .
Słowo ostrzeżenia
Zauważ, że inwersja zależności nie powinna być twoją podstawową odpowiedzią na logikę oddzielania. Poszukaj możliwości użycia zamiast tego parametryzacji . Rozważ tę metodę pseudokodu, na przykład:
Możemy użyć inwersji zależności dla niektórych części tej metody:
Ale nie powinniśmy, przynajmniej nie do końca. Zauważ, że stworzyliśmy klasę stanową
Querier
. Teraz zawiera odwołanie do jakiegoś zasadniczo globalnego obiektu połączenia. Powoduje to takie problemy, jak trudności w zrozumieniu ogólnego stanu programu i sposobu koordynacji między poszczególnymi klasami. Zwróć też uwagę, że jesteśmy zmuszeni sfałszować kwerendę lub połączenie, jeśli chcemy przetestować logikę uśredniania. Dalej Lepszym podejściem byłoby zwiększenie parametryzacji :Połączenie byłoby zarządzane na jeszcze wyższym poziomie, który jest odpowiedzialny za całą operację i wie, co zrobić z tym wyjściem.
Teraz możemy przetestować logikę uśredniania całkowicie niezależnie od zapytań, a ponadto możemy jej użyć w wielu różnych sytuacjach. Możemy zapytać, czy w ogóle potrzebujemy obiektów
MyQuerier
iAverager
, a może odpowiedź jest taka, że nie zrobimy tego, jeśli nie zamierzamy przeprowadzać testów jednostkowychStuffDoer
, a nie testowanie jednostkoweStuffDoer
byłoby całkowicie rozsądne, ponieważ jest tak ściśle powiązane z bazą danych. Bardziej sensowne byłoby po prostu pozwolić, aby testy integracji obejmowały to. W takim przypadku możemy być w porządkufetchAboveMin
iaverageData
stosować metody statyczne.źródło