Czy zgodnie z prawem Demeter klasa może zwrócić jednego ze swoich członków?

13

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)?

użytkownik2180613
źródło
9
Wiele osób (w szczególności programiści funkcjonalni) postrzega Prawo Demeter jako anty-wzór. Inni uważają to za „Czasami przydatne sugestie Demeter”.
David Hammen,
1
Głosuję za tym pytaniem, ponieważ ilustruje ono absurdy Prawa Demetera. Tak, LoD jest „czasami przydatne”, ale zwykle jest głupie.
user949300,
Jak się xszykuje?
candied_orange
@CandiedOrange xto po prostu inna właściwość, której wartością jest tylko jakiś obiekt, który można wywołać doSomethingElse.
user2180613,
1
Twój przykład nie narusza LOD. LOD polega na zapobieganiu sprzężeniu klientów obiektu z wewnętrznymi danymi obiektu lub współpracownikami. chcesz uniknąć takich rzeczy user.currentSession.isLoggedIn(). ponieważ pary klienci usernie tylko user, ale także jego współpracownik session. Zamiast tego chcesz móc pisać user.isLoggedIn(). zwykle osiąga się to przez dodanie isLoggedInmetody, userktórej implementacja deleguje currentSession.
Jonah

Odpowiedzi:

7

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.

Jeremy
źródło
1
Czy DDD zapewnia jakieś zasady określające, kiedy członkowie klasy, tacy jak dane, mogą zostać ujawnieni (tzn. Powinni pobrać istoty pobierające)? Wszystkie dyskusje wokół Law of Demeter, tell, don't ask, getters are evil, object encapsulationi single responsibility principlewydaje 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.
user2180613,
Istnieją takie wytyczne, jak „osoby pobierające są złe”, ponieważ wiele osób traktuje instancje klasowe jako struktury danych (worek danych do przekazania). To nie jest programowanie obiektowe. Obiekty to pakiety funkcjonalności / zachowania, które faktycznie zapewniają pewną pracę. Zakres pracy określa odpowiedzialność. Ta praca oczywiście stworzy obiekty, które zostaną zwrócone z metod itp. Jeśli twoja klasa faktycznie wykonuje znaczącą pracę, którą możesz wyraźnie zdefiniować poza „reprezentuj obiekt danych X z atrybutami Y i Z”, to jesteś na dobrej drodze . Nie stresowałbym się tym.
Jeremy,
Aby rozwinąć tę kwestię, kiedy używasz wielu programów pobierających / pytających, zwykle oznacza to, że masz gdzieś dużą metodę, która koordynuje wszystko, co Fowler nazywa skryptem transakcji. Zanim zaczniesz używać gettera, zadaj sobie pytanie ... dlaczego pytam o te dane z obiektu? Dlaczego ta funkcja nie jest metodą w obiekcie? Jeśli ta klasa nie jest odpowiedzialna za wdrożenie tej funkcjonalności, skorzystaj z gettera. Książka [Refaktoryzacja] ( martinfowler.com/books/refactoring.html ) autorstwa Fowlera to książka o heurystyce dla takich pytań.
Jeremy,
W swojej książce „Implementing Domain-Driven Design” Vaughn Vernon wspomina o prawie demetera i mówi „Nie pytaj” w rozdz. 10, s. 382. Może warto go zobaczyć, jeśli masz do niego dostęp.
Jeremy,
6

Czy zgodnie z prawem Demeter klasa może zwrócić jednego ze swoich członków?

Tak, z pewnością tak jest.

Spójrzmy na główne punkty :

  • Każda jednostka powinna mieć ograniczoną wiedzę na temat innych jednostek: tylko jednostki „ściśle” związane z bieżącą jednostką.
  • Każda jednostka powinna rozmawiać tylko z przyjaciółmi; nie rozmawiaj z nieznajomymi.
  • Rozmawiaj tylko z najbliższymi przyjaciółmi.

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.

Oprócz klas, 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łoby to prawo demeter (1) ?

To tylko stwarza okazję do naruszenia demeter. To nie jest naruszenie. To niekoniecznie jest złe.

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 (2)?

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.

W pseudokodzie:

Podejrzewam, że prawo Demetera zabrania takiego wzoru jak powyższy. Co mogę zrobić, aby wywołać funkcję doSomethingElse () bez naruszenia prawa (3)?

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ąc B) i wiesz, jak go użyć A. Cóż, teraz Ai Blepiej 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 Ajak coś można użyć Ai nie obchodziło, skąd pochodzi i Bmogli żyć szczęśliwie i wolne od swojej obsesji z uzyskaniem Aod B.

Jeśli chodzi o x.doSomethingElse(a)szczegóły bez xź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.

candied_orange
źródło
Płynne interfejsy nie są wyjątkiem od LOD ze względu na pewne pojęcie przyjazności ... W płynnym interfejsie każda z metod w łańcuchu jest wywoływana na tym samym obiekcie, ponieważ wszystkie zwracają siebie. settings.darkTheme().monospaceFont()jest tylko składniowym cukrem dlasettings.darkTheme(); settings.monospaceFont();
Jonah
3
@Jonah Powracająca osobowość to najwyższa życzliwość. I nie wszystkie płynne interfejsy. Wiele iDSL jest również płynnych i zwraca różne typy, aby zmienić dostępne metody w różnych punktach. Rozważmy jOOQ , kreatora kroków , kreatora kreatorów i mojego własnego koszmarnego konstruktora potworów . Biegła to styl. Nie wzór.
candied_orange
2

Oprócz zajęć, które zostały specjalnie wyznaczone do zwrotu przedmiotów [...]

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.

NikoGJ
źródło
0

Oprócz klas, które zostały specjalnie wyznaczone do zwracania obiektów - takich jak klasy fabryki i konstruktora - czy metoda jest w porządku dla metody?

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ć.

Robert Harvey
źródło
Przykład dotyczy niejawnego b.getA().doSomething()ix.doSomethingElse(b.getA())
user2180613
A a = b.getA(); a.DoSomething();- z powrotem do jednej kropki.
Robert Harvey,
2
@ user2180613 - Jeśli chcesz zapytać b.getA().doSomething()i x.doSomethingElse(b.getA())powinieneś wyraźnie o to zapytać. W tej chwili wydaje się, że twoje pytanie dotyczy this.b.getA().
David Hammen,
1
Ponieważ Anie jest członkiem C, nie jest utworzony Ci nie jest mu zapewniony C, wydaje się, że używanie Aw C(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łcenie Driver().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, tj tell don't ask. Zwracane wartości wydają się być z tym sprzeczne.
user2180613,
1
Przypuszczam, że to prawda, ale nie odpowiada na pytanie.
user2180613,
0

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.

gnasher729
źródło