Istnieje klasyczny problem OOP łączenia łańcuchów metod z metodami „pojedynczego punktu dostępu”:
main.getA().getB().getC().transmogrify(x, y)
vs
main.getA().transmogrifyMyC(x, y)
Pierwszy wydaje się mieć tę zaletę, że każda klasa odpowiada tylko za mniejszy zestaw operacji i czyni wszystko o wiele bardziej modułowym - dodanie metody do C nie wymaga żadnego wysiłku w A, B lub C, aby ją ujawnić.
Minusem jest oczywiście słabsza enkapsulacja , którą rozwiązuje drugi kod. Teraz A ma kontrolę nad każdą metodą, która przez nią przechodzi, i może delegować ją do swoich pól, jeśli chce.
Zdaję sobie sprawę, że nie ma jednego rozwiązania i oczywiście zależy to od kontekstu, ale naprawdę chciałbym usłyszeć jakieś uwagi na temat innych ważnych różnic między tymi dwoma stylami i pod jakimi warunkami powinienem preferować którykolwiek z nich - ponieważ teraz, kiedy próbuję aby zaprojektować jakiś kod, wydaje mi się, że po prostu nie używam argumentów do decydowania w ten czy inny sposób.
Zasadniczo staram się ograniczyć metodę łączenia łańcuchów (na podstawie Prawa Demetera )
Jedyny wyjątek, jaki robię, to płynne interfejsy / wewnętrzne programowanie w stylu DSL.
Martin Fowler dokonuje tego samego rozróżnienia w językach specyficznych dla domeny, ale z powodu naruszenia separacji zapytań polecenia, która stwierdza:
Fowler w swojej książce na stronie 70 mówi:
źródło
Myślę, że pytanie brzmi, czy używasz odpowiedniej abstrakcji.
W pierwszym przypadku mamy
Gdzie główny jest typu
IHasGetA
. Pytanie brzmi: czy ta abstrakcja jest odpowiednia. Odpowiedź nie jest trywialna. I w tym przypadku wygląda to trochę nie tak, ale i tak jest to teoretyczny przykład. Ale skonstruować inny przykład:Jest często lepszy niż
Ponieważ w drugim przykładzie zarówno
this
imain
zależy od rodzajuv
,w
,x
,y
iz
. Ponadto kod nie wygląda tak naprawdę lepiej, jeśli każda deklaracja metody zawiera pół tuzina argumentów.Lokalizator usług faktycznie wymaga pierwszego podejścia. Nie chcesz uzyskać dostępu do instancji, którą tworzy za pośrednictwem lokalizatora usług.
Tak więc „sięgnięcie” przez obiekt może stworzyć wiele zależności, tym bardziej, jeśli jest ono oparte na właściwościach rzeczywistych klas.
Jednak tworzenie abstrakcji, polegające na zapewnieniu obiektu, jest czymś zupełnie innym.
Na przykład możesz mieć:
Gdzie
main
jest przykładMain
. Jeśli znajomość klasymain
zmniejszy zależność doIHasGetA
zamiastMain
, przekonasz się, że sprzężenie faktycznie jest dość niskie. Kod wywołujący nawet nie wie, że w rzeczywistości wywołuje ostatnią metodę na oryginalnym obiekcie, co faktycznie ilustruje stopień oddzielenia.Sięgasz ścieżką zwięzłych i ortogonalnych abstrakcji, a nie w głąb implementacji.
źródło
Prawo Demeter , jak @ Péter Török zaznacza, sugeruje „Compact” formę.
Ponadto, im więcej metod zostanie wyraźnie wspomnianych w kodzie, tym więcej klas zależy od klasy, co zwiększa problemy z konserwacją. W twoim przykładzie zwarta forma zależy od dwóch klas, podczas gdy dłuższa forma zależy od czterech klas. Dłuższa forma nie tylko narusza Prawo Demetera; spowoduje to również zmianę kodu za każdym razem, gdy zmienisz jedną z czterech metod, do których się odwołujesz (w przeciwieństwie do dwóch w zwartej formie).
źródło
A
eksploduje, a wiele metod i takA
może chcieć oddelegować. Mimo to zgadzam się z zależnościami - to drastycznie zmniejsza ilość zależności wymaganych od kodu klienta.Sam zmagałem się z tym problemem. Wadą „sięgania” głęboko w różne obiekty jest to, że podczas refaktoryzacji będziesz musiał zmienić strasznie dużo kodu, ponieważ istnieje tak wiele zależności. Ponadto kod staje się nieco rozdęty i trudniejszy do odczytania.
Z drugiej strony posiadanie klas, które po prostu „przekazują” metody, oznacza również narzut związany z deklarowaniem wielu metod w więcej niż jednym miejscu.
Rozwiązaniem, które łagodzi to i jest odpowiednie w niektórych przypadkach, jest posiadanie klasy fabrycznej, która buduje swego rodzaju obiekt fasadowy poprzez kopiowanie danych / obiektów z odpowiednich klas. W ten sposób możesz zakodować obiekt fasadowy, a po refaktoryzacji wystarczy zmienić logikę fabryki.
źródło
Często stwierdzam, że logika programu jest łatwiejsza do opanowania za pomocą metod łańcuchowych. Dla mnie
customer.getLastInvoice().itemCount()
lepiej pasuje do mojego mózgucustomer.countLastInvoiceItems()
.Niezależnie od tego, czy jest to warte konserwacji, musisz mieć dodatkowe sprzęgło. (Lubię też małe funkcje w małych klasach, więc mam tendencję do tworzenia łańcuchów. Nie mówię, że to prawda - po prostu to robię.)
źródło