Mam trzy pytania dotyczące prawa Demeter.
Poza klasami, które zostały specjalnie wyznaczone do zwracania obiektów - takich jak klasy fabryczne i konstrukcyjne - czy metoda jest w stanie zwrócić obiekt, np. Obiekt będący własnością jednej z właściwości klasy, czy też narusza to prawo demeter (1) ? A jeśli narusza prawo demeter, czy miałoby to znaczenie, czy zwracany obiekt jest niezmiennym obiektem, który reprezentuje kawałek danych i zawiera tylko getery dla tych danych (2a)? A może taki ValueObject sam w sobie jest anty-wzorcem, ponieważ wszystko, co dzieje się z danymi w tej klasie, odbywa się poza klasą (2b)?
W pseudokodzie:
class A {}
class B {
private A a;
public A getA() {
return this.a;
}
}
class C {
private B b;
private X x;
public void main() {
// Is it okay for B.getA to exist?
A a = this.b.getA();
a.doSomething();
x.doSomethingElse(a);
}
}
Podejrzewam, że prawo Demetera zabrania takiego wzoru jak powyższy. Co mogę zrobić, aby doSomethingElse()
zadzwonić, nie naruszając prawa (3)?
źródło
x
szykuje?x
to po prostu inna właściwość, której wartością jest tylko jakiś obiekt, który można wywołaćdoSomethingElse
.user.currentSession.isLoggedIn()
. ponieważ pary klienciuser
nie tylkouser
, ale także jego współpracowniksession
. Zamiast tego chcesz móc pisaćuser.isLoggedIn()
. zwykle osiąga się to przez dodanieisLoggedIn
metody,user
której implementacja delegujecurrentSession
.Odpowiedzi:
W wielu językach programowania wszystkie zwracane wartości są obiektami. Jak powiedzieli inni, niemożność korzystania z metod zwracanych obiektów zmusza cię do tego, aby nigdy niczego nie zwracać. Powinieneś zadać sobie pytanie: „Jakie są obowiązki klas A, B i C?” Właśnie dlatego przy użyciu nazw zmiennych metasyntaktycznych, takich jak A, B i C, zawsze można uzyskać odpowiedź „zależy”, ponieważ w tych kategoriach nie ma obowiązków dziedziczenia.
Być może warto przyjrzeć się projektowi opartemu na domenie, który zapewni bardziej szczegółowy zestaw heurystyk pozwalający ustalić, gdzie powinna iść funkcjonalność i kto powinien ją wywoływać.
Drugie pytanie dotyczące niezmiennych obiektów odnosi się do pojęcia
obiektu wartości w porównaniu do obiektu jednostki. w DDD możesz przekazywać obiekty wartości prawie bez żadnych ograniczeń. Nie dotyczy to obiektów encji.
Uproszczony LOD jest znacznie lepiej wyrażony w DDD jako zasady dostępu do agregatów . Żadne obiekty zewnętrzne nie powinny zawierać odniesień do elementów agregujących. Należy przechowywać tylko referencje root.
Oprócz DDD, chcesz przynajmniej opracować własne zestawy stereotypów dla swoich klas, które są rozsądne dla twojej domeny. Egzekwuj reguły dotyczące tych stereotypów podczas projektowania systemu.
Zawsze też pamiętaj, że wszystkie te zasady mają na celu zarządzanie złożonością, a nie hamowanie siebie.
źródło
Law of Demeter
,tell, don't ask
,getters are evil
,object encapsulation
isingle responsibility principle
wydaje się, sprowadzają się do tego pytania: kiedy delegacja do obiektu domeny powinien być stosowany w przeciwieństwie do coraz wartość obiektu i robi coś z tym wspólnego? Byłoby bardzo przydatne, aby móc zastosować niuansowe kwalifikacje w sytuacjach, w których taka decyzja musi zostać podjęta.Tak, z pewnością tak jest.
Spójrzmy na główne punkty :
Wszystkie trzy pozostawiają ci pytanie: kto jest przyjacielem?
Podejmując decyzję o zwrocie, Prawo Demetera lub zasada najmniejszej wiedzy (LoD) nie nakazuje, abyś bronił się przed programistami, które nalegają na jego naruszenie. Wymaga to, abyś nie zmuszał programistów do naruszania go.
Zrozumienie tych jest właśnie powodem, dla którego tak wielu uważa, że rozgrywający musi zawsze powrócić do pustki. Nie. Musisz umożliwić sposób tworzenia zapytań (getterów), które nie zmieniają stanu systemu. To podstawowa separacja zapytań poleceń .
Czy to oznacza, że możesz zagłębić się w bazę kodu łączącą ze sobą, co tylko chcesz? Nie. Połącz razem tylko to, co miało być połączone razem. W przeciwnym razie łańcuch może się zmienić i nagle twoje rzeczy zostaną zerwane. To właśnie rozumieją przyjaciele.
Można zaprojektować długie łańcuchy. Płynne interfejsy, iDSL, duża część Java8 i stary dobry StringBuilder mają na celu budowanie długich łańcuchów. Nie naruszają LoD, ponieważ wszystko w łańcuchu ma współpracować i obiecało, że będzie dalej działać. Naruszasz demeter, gdy łączysz ze sobą rzeczy, o których nigdy się nie słyszeli. Przyjaciele to ci, którzy obiecali, że twój łańcuch będzie działał. Przyjaciele Przyjaciół nie.
To tylko stwarza okazję do naruszenia demeter. To nie jest naruszenie. To niekoniecznie jest złe.
Niezmienne jest dobre, ale nie ma tu znaczenia. Poruszanie się po dłuższym łańcuchu nie poprawia sytuacji. Lepiej jest oddzielić pobieranie od używania. Jeśli używasz, zapytaj o to, czego potrzebujesz jako parametru. Nie poluj na nie, zagłębiając się w porywaczy nieznajomych.
Zanim zacznę mówić o
x.doSomethingElse(a)
zrozumieniu, że zasadniczo napisałeśb.getA().doSomething()
Teraz LoD nie jest ćwiczeniem zliczania kropek . Ale kiedy tworzysz łańcuch, mówisz, że wiesz, jak zdobyć
A
(używającB
) i wiesz, jak go użyćA
. Cóż, terazA
iB
lepiej bądźcie bliskimi przyjaciółmi, ponieważ po prostu połączyłeś ich ze sobą.Jeśli właśnie poprosił o coś do ręki ci
A
jak coś można użyćA
i nie obchodziło, skąd pochodzi iB
mogli żyć szczęśliwie i wolne od swojej obsesji z uzyskaniemA
odB
.Jeśli chodzi o
x.doSomethingElse(a)
szczegóły bezx
źródła, LoD nie ma nic do powiedzenia na ten temat.LoD może zachęcać do oddzielania użytkowania od konstrukcji . Ale zaznaczę, że jeśli religijnie traktujesz każdy obiekt jako nieprzyjazny, utkniesz w pisaniu kodu metodami statycznymi. Możesz w ten sposób zbudować cudownie złożony wykres obiektowy, ale ostatecznie musisz wywołać na nim metodę, aby rozpocząć pracę. Musisz po prostu zdecydować, kim są twoi przyjaciele. Nie da się od tego uciec.
Tak więc klasa może zwrócić jednego ze swoich członków na podstawie LoD. Kiedy to zrobisz, powinieneś wyjaśnić, czy ten członek jest przyjazny dla twojej klasy, ponieważ jeśli nie, jakiś klient może spróbować połączyć cię z tą klasą, używając ciebie, aby ją zdobyć przed użyciem. To ważne, ponieważ teraz to, co zwracasz, musi zawsze obsługiwać to użycie.
Istnieje wiele przypadków, w których po prostu nie stanowi to problemu. Kolekcje mogą to zignorować po prostu dlatego, że mają być przyjaciółmi ze wszystkim, co ich używa. Podobnie obiekty wartości są przyjazne dla wszystkich. Ale jeśli napisałeś narzędzie do sprawdzania poprawności adresu, które wymagało od obiektu pracownika, z którego wyodrębniał adres, a następnie po prostu poprosił o adres, lepiej miej nadzieję, że zarówno pracownik, jak i adres pochodzą z tej samej biblioteki.
źródło
settings.darkTheme().monospaceFont()
jest tylko składniowym cukrem dlasettings.darkTheme(); settings.monospaceFont();
Nie do końca rozumiem ten argument. Każdy obiekt może zwrócić obiekt w wyniku wywołania komunikatu. Zwłaszcza jeśli myślisz w „wszystko jako przedmiot”.
Klasy są jednak jedynymi obiektami, które mogą tworzyć nowe obiekty własnego rodzaju, wysyłając im wiadomość „nową” (lub często zastępowaną nowym słowem kluczowym w najpopularniejszych językach OOP)
W każdym razie, jeśli chodzi o twoje pytanie, byłbym raczej podejrzliwy wobec obiektu zwracającego jedną ze swoich właściwości, aby można go było użyć w innym miejscu. Może się zdarzyć, że obiekt ten wycieknie informacji o jego stanie lub szczegółach implementacji.
I nie chciałbyś, aby obiekt C wiedział o szczegółach implementacji B. Jest to niepożądana zależność od obiektu A z perspektywy obiektu C.
Możesz więc zadać sobie pytanie, czy C, znając A, nie wie zbyt wiele dla pracy, którą musi wykonać, niezależnie od LOD.
Są przypadki, w których może to być uzasadnione, na przykład zapytanie obiektu kolekcji o jeden z jego elementów jest właściwe i nie łamanie żadnej reguły Demeter. Z drugiej strony zapytanie obiektu Book o jego obiekt SQLConnection jest ryzykowne i należy go unikać.
LOD istnieje, ponieważ wielu programistów zwykle prosi obiekty o informacje przed wykonaniem z nich pracy. LOD ma nam przypomnieć, że czasami (jeśli nie zawsze) nadmiernie komplikujemy rzeczy, chcąc, aby nasze obiekty robiły za dużo. Czasami musimy po prostu poprosić o inny przedmiot, aby wykonał za nas pracę. LOD pozwala nam zastanowić się dwa razy nad tym, gdzie umieszczamy zachowanie w naszych obiektach.
źródło
Dlaczego by nie miał Wydaje się, że sugerujesz, że konieczne jest zasygnalizowanie innym programistom, że klasa ma specjalnie zwracać obiekty, w przeciwnym razie nie może zwracać obiektów. W językach, w których wszystko jest przedmiotem, poważnie ograniczyłoby to twoje opcje, ponieważ nie byłbyś w stanie niczego zwrócić.
źródło
b.getA().doSomething()
ix.doSomethingElse(b.getA())
A a = b.getA(); a.DoSomething();
- z powrotem do jednej kropki.b.getA().doSomething()
ix.doSomethingElse(b.getA())
powinieneś wyraźnie o to zapytać. W tej chwili wydaje się, że twoje pytanie dotyczythis.b.getA()
.A
nie jest członkiemC
, nie jest utworzonyC
i nie jest mu zapewnionyC
, wydaje się, że używanieA
wC
(w jakikolwiek sposób) narusza LoD. Liczenie kropek nie jest tym, o co chodzi w LoD, jest jedynie narzędziem ilustracyjnym. Możliwe jest przekształcenieDriver().getCar().getSteeringWheel().getFrontWheels().toRight()
w osobne instrukcje, z których każda zawiera jedną kropkę, ale naruszenie LoD nadal istnieje. Czytałem w wielu postach na tym forum, że wykonywanie czynności powinno być delegowane do obiektu, który realizuje rzeczywiste zachowanie, tjtell don't ask
. Zwracane wartości wydają się być z tym sprzeczne.Zależy od tego, co robisz. Jeśli ujawnisz członka swojej klasy, kiedy to nie jest sprawą niczyją, że jest on członkiem twojej klasy lub jaki jest typ tego członka, to źle. Jeśli następnie zmienisz typ tego elementu i typ funkcji zwracającej element, a wszyscy będą musieli zmienić swój kod, to jest złe.
Z drugiej strony, jeśli interfejs twojej klasy stwierdza, że dostarczy instancję dobrze znanej klasy A, to dobrze. Jeśli masz szczęście i możesz to zrobić, dostarczając członka swojej klasy, to dobrze. Jeśli zmieniłeś tego członka z B na B, będziesz musiał przepisać metodę, która zwraca A, oczywiście będzie musiał teraz zbudować jeden i wypełnić go dostępnymi danymi, zamiast zwracać tylko jeden wiersz Członek. Interfejs twojej klasy pozostałby niezmieniony.
źródło