Wstrzykiwanie zależności: czy powinienem stworzyć klasę samochodów, która pomieści wszystkie jej części?

10

Mam wiele samochodów w mojej aplikacji C ++, wszystkie zawarte w RaceTrack.

Każdy samochód składa się z setek części. Każda część zależy od innej części lub dwóch.

Przeczytałem wiele pytań SO na temat książki DI i Marka Seemanna i wygląda na to, że nie powinienem definiować klasy samochodów tylko po to, by przechowywać części samochodowe, ponieważ wszystkie części samochodowe będą od siebie zależeć, a ta zupa części jest samochodem. Czy mam rację?

Więc kiedy umieszczę wszystkie moje samochody wyścigowe w RaceTrack, nie będzie żadnych podmiotów samochodowych, ale będzie wiele części samochodowych zależnych od siebie, ścigających się na torze? Czy mam rację?

EDYTOWAĆ:

Przez wieki programowałem klasy samochodów, ponieważ dla mnie było oczywiste, że potrzebuję klasy samochodu, jeśli programuję logikę samochodu. Ale z DI nie jest to dla mnie tak oczywiste. Nadal zastanawiasz się, czy to idiomatyczne dla DI, aby nie tworzyć klasy samochodów, jeśli nie mam dla niej określonej roli?

Mam na myśli, czy w porządku jest mieć koło kierownicy do jazdy, śruby BoltsAndNuts na kołach dla załogi pitnej i wszelkiego rodzaju inne śmieszne interfejsy bez instancji reprezentującej samochód jako całość?

Anton Pietrow
źródło

Odpowiedzi:

24

Jaka jest korzyść ze strony części samochodowych siedzących na torze wyścigowym?

Jeśli cała twoja klasa samochodów zajmuje się przechowywaniem części samochodowych, jest tak samo przydatna jak mokra torba części.

Stosowanie

Jako kierowca chcę kontrolować. Odpowiada, kiedy proszę o prędkość. To działa jak na szynach. To może skończyć się na dziesięciocentówce.

To, czego chcę, to klasa samochodów, która będzie użyteczna. Że mogę powiedzieć, żeby robić rzeczy bez zastanawiania się, jak działa gaźnik. Myślę tylko o pedale gazu. To, jak są one połączone, nie jest czymś, o co się martwię. To jest abstrakcja.

Wstrzykiwanie zależności

Zastrzyk zależności nie ma z tym nic wspólnego. Kiedy prowadzę samochód, nie myślę o tym, jak został zbudowany. Tak długo, jak to działa, nie obchodzi mnie, jak je poskładają.

Nie, DI jest tym, co pozwala mojej załodze pitnej na szybką wymianę opon na lepsze, gdy zacznie padać deszcz na torze. Fajnie jest móc to zrobić bez wskakiwania do zupełnie innego samochodu.

DI naprawdę polega na przestrzeganiu zasady: Oddziel użycie od konstrukcji.

Samochód, który może montować nowe opony z prędkością 90 mil na godzinę, może brzmieć fajnie, ale nie sądzę, że wygra jakiekolwiek wyścigi z oponą instalującą na nim opony.

DI polega na zainstalowaniu części w sposób, który pozwoli Twojej załodze do nich dotrzeć. Kiedy używasz neww tym samym miejscu, programujesz zachowanie, to tak, jakbyś spawał gaźnik na swoim miejscu. Upewnij się, że palnik acetylenowy może go usunąć, ale najpierw rozważ użycie nakrętek i śrub.

Właśnie tym jest DI. Jasne, że nie jest to tak łatwe, jak newwynalezienie czegoś, gdy tylko zorientujesz się, że tego chcesz. Zamiast tego musisz napisać osobny kod, który wie, jak zbudować samochód. Ale z pewnością ułatwia późniejsze zmiany. Oznacza to, że nie musisz ciągnąć ze sobą zakładu montażu samochodów na torze.

Budowa

Coś musi wiedzieć, czy opony to Goodyear. Gdzie zatem umieścić kod budowy samochodu? Jeśli nie samochód, to załoga pit? Nie. Tor? Nie. Wszystkie mają kod zachowania. Kod, który musi wykonać podczas wyścigu. Konstrukcja samochodu powinna nastąpić przed wyścigiem w miejscu usuniętym z kodu zachowania. Mark Seemans nazwał to miejsce Korzeniem Kompozycji . Większość ludzi nazywa to głównym.

To prosty wzór. Zasadniczo skonstruuj wykres obiektowy, a następnie wywołaj jedną metodę behawioralną na jednym obiekcie na wykresie obiektowym. Otóż ​​to. Jest to jedyne miejsce, w którym konstrukcja i zachowanie muszą być razem.

To nie znaczy, że konstrukcja musi być stosem kodu proceduralnego, wszystkie ułożone sekwencyjnie w głównej kolejności. Możesz swobodnie korzystać z każdego narzędzia w języku do budowania. Tylko nie mieszaj tego z zachowaniem.

Robiąc to w języku i nie używając jakiegoś frameworka DI lub kontenera IoC nazywa się czystym DI . Pracuje bardzo dobrze. Ma od dawna. Nazywaliśmy to po prostu przekazywaniem referencji .

Narzędzia DI

To, co kupuje narzędzie DI, to szczegóły konstrukcyjne przeniesione na inny język (xml, json, cokolwiek), który wymusza oddzielenie konstrukcji od zachowania. Jeśli nie ufasz innym programistom, że nie używają, newkiedy nie powinni, może to być atrakcyjne.

Wadą jest to, że kuszące jest, aby szczegóły narzędzia DI rozprzestrzeniały się w całej bazie kodu. Czasami infekuje bazę kodu zastrzeżonymi adnotacjami. Podane przez nich przykłady z pewnością to zachęcają. Narzędzie ma tendencję do poruszania się w przestrzeni językowej, dopóki nie będzie można reklamować zadania jako zadania programowania Java, ale jako zadania programowania Java / Spring.

Zasady projektowania

Przez wieki programowałem klasy samochodów, ponieważ dla mnie było oczywiste, że potrzebuję klasy samochodu, jeśli programuję logikę samochodu. Ale z DI nie jest to dla mnie tak oczywiste. Nadal zastanawiasz się, czy to idiomatyczne dla DI, aby nie tworzyć klasy samochodów, jeśli nie mam dla niej określonej roli?

Myślę, że uczysz się o abstrakcji i zmieniasz sposób, w jaki decydujesz, że klasa jest potrzebna. Dobre. Ale to nie dotyczy DI. DI nie pomaga zdecydować, czy potrzebujesz klasy samochodu. DI pomaga nie dopuścić do tego, aby samochód wiedział, a tym samym dbałości, jeśli opony są oponami Goodyear. DI pomaga ci nie wiedzieć, czy samochody są wyprodukowane w Japonii.

Jednym z najbardziej fundamentalnych pytań w projektowaniu oprogramowania jest „co o czym wiadomo?” To jest najważniejsze, co pokazuje diagram UML. Kiedy odkrywasz coś, co osiągasz, jest to interfejs do konkretnej rzeczy, z którą jesteś teraz związany. Samochód musi teraz wiedzieć, że opony to Goodyear. Co jest do bani, jeśli Michelin chce cię sponsorować.

Unikanie tego jest nazywane zgodnie z zasadą inwersji zależności . Formalnie moduł wysokiego poziomu (taki jak klasa samochodu) nie powinien bezpośrednio zależeć od modułów niskiego poziomu (takich jak klasa GoodyearTire). Powinno to zależeć od abstrakcji (jak interfejs opon).

Sposobem na uniknięcie tego jest odwrócenie kontroli . Tutaj nacisk kładzie się na zmianę przepływu kontroli. Czy opony poruszają samochodem, czy samochód porusza oponami? Właściwe myślenie o tym pozwala nam nie statycznie łączyć samochodu i opon. DI jest jednym szczególnym sposobem na śledzenie odwrócenia kontroli.

Nic z tego nie mówi, jeśli potrzebujesz klasy samochodu. Jeśli programujesz „logikę samochodową”, dobrze jest, jeśli istnieje jedno miejsce, aby je zachować, a nie rozrzucać je wszędzie. Po prostu nie daj się zwieść myśleniu, że logika budowy samochodu jest taka sama jak logika zachowania samochodu, więc wszystko musi żyć w tym samym miejscu. Ale jeśli nie masz zdefiniowanego rzutu na samochód, to też nie potrzebujesz. Ścigaj się motocyklami po torze, jeśli chcesz.

Mam na myśli, czy w porządku jest mieć koło kierownicy do jazdy, śruby BoltsAndNuts na kołach dla załogi pitnej i wszelkiego rodzaju inne śmieszne interfejsy bez instancji reprezentującej samochód jako całość?

DI lub brak DI, dobrze jest mieć instancję, która reprezentuje samochód jako całość, ale ta instancja nie jest czymś, o czym chcę wiedzieć bezpośrednio, jeśli nie muszę. Daj mi abstrakcję samochodu, abym nie musiał się przejmować, czy podczas pracy będzie zasilany gazem, olejem napędowym lub elektrycznością. To jest coś, o co powinienem dbać, kiedy go buduję lub utrzymuję. Fajnie, jeśli kod używający samochodu nie musi wiedzieć ani obchodzić się, jak to działa. „Nie wiem. Nie chcę wiedzieć.”

candied_orange
źródło
Byłoby fajnie, gdybyś nie użył metafory załogi samochodu i załogi do pytania, które używa ich do przedstawienia kodu. Ale wciąż dobra odpowiedź.
S. Tarık Çetin
6
I zawsze myślałem, że Inversion of Control miał miejsce, gdy
skręcasz w
CandiedOrange, więc nie ma klasy samochodu, jeśli nie ma sensownego interfejsu i jasno określonej odpowiedzialności? I nie ma Car.h w moim projekcie. A kolega, który zagląda do mojego kodu i zastanawia się, czy jest to sklep internetowy z częściami samochodowymi czy aplikacja do zarządzania garażem? :)
Anton Pietrow
@AntonPetrov „jeśli wygląda jak kaczka i kwacze jak kaczka, ale potrzebuje baterii, prawdopodobnie masz niewłaściwą abstrakcję”. Dobre imiona są bardzo ważne i mogą być trudne do wymyślenia, ale należy je zmienić, aby odzwierciedlić dobrze zdefiniowaną odpowiedzialność i sensowny interfejs, aby nie były zaskoczeniem. Jeśli przeczytam nazwę, spójrz na interfejs i pomyślę, że „prawie tego się spodziewałem”, więc masz dobre imię.
candied_orange 16.04.17
15

wygląda na to, że nie powinienem definiować klasy samochodów tylko do przechowywania części samochodowych, ponieważ wszystkie części samochodowe będą od siebie zależeć, a ta zupa części jest samochodem. Czy mam rację?

Nie.

Punkt zastrzyku zależności wcale nie ma na celu zniechęcenia do zależności. Wręcz przeciwnie.

Wtrysk zależność dotyczy pytanie: skąd samochód dostać jego części z ? Gdzie i jak są tworzone i jak samochód nawiązuje do nich?

Naiwnie samochód sam tworzył swoje części, dzwoniąc new. Jest to łatwe i jednoznaczne, ale jest bardzo mało elastyczne. Samochód musi wiedzieć wszystko, co jest konieczne do stworzenia części, a jeśli kiedykolwiek chcesz mieć dwa samochody z różnymi częściami, logika musi również przejść do samochodu.

Dzięki wstrzykiwaniu zależności cała logika jest usuwana z samochodu i umieszczana w komponencie konfiguracji, który tworzy wszystkie części i przewody w górę referencji. W pełni skonfigurowane zależności są wprowadzane do samochodu - i do siebie nawzajem.

Michael Borgwardt
źródło
1
Wreszcie rozsądne wyjaśnienie dependency injectionpojęcia.
anatoly techtonik
1
Powiedziałbym, że bardziej powszechnym systemem podejścia „naiwnego” nie jest to, że samochód nazwałby nowy, ale że użytkownik klasy samochodu przypisałby swoje części swoim właściwościom, a tymczasem klasa samochodu byłaby w stan nieokreślony. DI zapewnia środki do mechanicznego zapewnienia ważności klasy.
whatsisname
2
@whatsisname Za pomocą DI mogę łatwo tworzyć niepoprawne i w połowie zainicjowane obiekty. Walidacja nie jest obowiązkiem DI. Nie jest też niezmienny. DI może podjąć się budowy prawidłowych i niezmiennych obiektów. DI nie ma możliwości wymuszenia, że ​​są to jedyne sposoby konstruowania i używania tych obiektów. Obiekty muszą to wymusić.
candied_orange
@CandiedOrange: dzięki czemu możesz łatwo użyć dowolnej techniki, aby zrobić coś na wpół osaczonego, więc co? Wymaganie zależności od konstruktora i uczynienie ich niezmiennymi nie jest srebrną kulą, ale jest to jednak bardzo przydatny sposób zapobiegania wielu typowym złym sytuacjom.
whatsisname
0

Więc kiedy umieszczę wszystkie moje samochody wyścigowe w RaceTrack, nie będzie żadnych podmiotów samochodowych, ale będzie wiele części samochodowych zależnych od siebie, ścigających się na torze? Czy mam rację?

Dobrze. Tak i nie. Podmiot samochodowy jest nadal ważny do posiadania. A samochód to coś więcej niż suma jego części. Potrzebujesz też kierowcy (na razie). Wiele razy samochody wyścigowe mają również nazwy, nie wspominając o marce i modelu samochodu.

Tak, samochody są w zasadzie pojemnikiem na części, ale to właśnie opakowanie i współpraca części składają się na coś większego: samochód.

Więc naprawdę nie. Samochód to nie tylko przypadkowa torba z metalu i plastiku. To maszyna.

W tym przypadku wstrzyknięcie zależności nie jest przesadą. Coś innego musi zbudować samochód, którym byłby obiekt klasy fabrycznej lub obiekt Builder. Samochód nie powinien wiedzieć, jak sam się zbudować.

Greg Burghardt
źródło
2
Możesz mieć DI bez fabryk lub obiektów konstruktora. Proste parametry konstruktora to poprawna metoda, która działa w wielu okolicznościach.
whatsisname