2009-07-20 13 views
5

Obiekt Law of Demeter nie zapobiega przekazywaniu obiektów do konstruktorów klas. Zabrania jednak pobierania tego samego obiektu później i wywoływania na nim metody w celu uzyskania wartości skalarnej. Zamiast tego należy utworzyć metodę proxy, która zamiast tego zwraca wartość skalarną. Moje pytanie brzmi: dlaczego dopuszczalne jest przekazanie obiektu do konstruktora klasy, ale nie można go zaakceptować, aby uzyskać ten sam obiekt później i pobrać z niego wartość?Prawo konstruktorów Demeter i klasy

+0

Czy możesz podać link do "Prawa średnicy"? – gahooa

Odpowiedz

13

Ponieważ Prawo Demeter mówi, że nie powinieneś projektować zewnętrznego interfejsu obiektu, aby wyglądał tak, jakby składał się z pewnych innych obiektów ze znanymi interfejsami, klienci mogą po prostu chwycić i uzyskać dostęp.

Przekazujesz obiekt do konstruktora, aby poinformować nowy obiekt, jak się zachowuje, ale to nie twoja firma, niezależnie od tego, czy obiekt utrzymuje ten obiekt parametru, czy zachowuje jego kopię, czy tylko raz go obserwuje i zapomina, że ​​kiedykolwiek istniało. Korzystając z metody getMyParameterBack, wszystkie przyszłe implementacje zostały zatwierdzone, aby móc produkować cały obiekt na żądanie, a wszystkie klienty połączyć w dwa interfejsy zamiast jednego.

Na przykład, jeśli przekazujesz parametr URL do konstruktora obiektu HTTPRequest, to nie znaczy, że HTTPRequest powinien mieć metodę getURL, która zwraca obiekt URL, od którego wywołujący ma następnie wywołać getProtocol, getQueryString, itd. Jeśli ktoś, kto ma obiekt HTTPRequest, może chcieć znać protokół żądania, powinien (jak mówi Law) dowiedzieć się, wywołując getProtocol na obiekcie, który ma, a nie na innym obiekcie, o którym wiedzą, że przechowywany jest protokół HTTPRequest wewnętrznie.

Chodzi o to, aby zmniejszyć sprzężenie - bez prawa Demeter użytkownik musi znać interfejs do HTTPRequest i URL, aby uzyskać protokół. Zgodnie z prawem, potrzebują interfejsu tylko do HTTPRequest. I HTTPRequest.getProtocol() wyraźnie może zwrócić "http" bez potrzeby jakiegoś obiektu URL do włączenia się w dyskusję.

Fakt, że czasem użytkownik obiektu żądania jest tym, który go utworzył, a zatem używa również interfejsu URL w celu przekazania parametru, nie jest ani tutaj, ani tam. Nie wszyscy użytkownicy obiektów HTTPRequest utworzyli je samodzielnie. Klienci, którzy zgodnie z prawem mają dostęp do adresu URL, ponieważ sami go stworzyli, mogą zrobić to w ten sposób, zamiast odzyskiwać je z Żądania. Klienci, którzy nie utworzyli adresu URL, nie mogą tego zrobić.

Osobiście uważam, że prawo Demeter, jak zwykle jest napisane w prostej formie, jest złamane. Czy naprawdę twierdzą, że jeśli mój obiekt ma pole Nazwa łańcucha i chcę wiedzieć, czy Nazwa zawiera jakiekolwiek znaki spoza ASCII, to albo muszę zdefiniować metodę NameContainsNonASCIICharacters na moim obiekcie zamiast patrzeć na sam napis, albo też dodać funkcję visitName do klasy przyjmującej funkcję wywołania zwrotnego w celu obejścia ograniczenia, upewniając się, że ciąg znaków jest parametrem funkcji, którą napisałem? To wcale nie zmienia sprzężenia, po prostu zastępuje metody gettera metodami odwiedzającymi. Czy każda klasa, która zwraca liczbę całkowitą, ma pełny zestaw operacji arytmetycznych, na wypadek gdyby chciałbym manipulować wartością zwracaną? getPriceMultipliedBy (int n)? Na pewno nie.

To, co jest przydatne, polega na tym, że gdy go zerwiesz, możesz zadać sobie pytanie, dlaczego go łamiesz i czy możesz zaprojektować lepszy interfejs, nie łamiąc go. Często możesz, ale tak naprawdę to zależy od rodzaju obiektów, o których mówisz. Pewne interfejsy można bezpiecznie łączyć z obszernymi obszarami kodu - takimi jak liczba całkowita, ciąg znaków, a nawet adres URL, które reprezentują powszechnie używane pojęcia.

+0

Widzisz, opakowanie Uri jest dokładnie typem wszechobecnej klasy, która byłaby zwolniona z tej reguły, podobnie jak ciąg znaków. Postrzegany jako środek do końca, jest to dobra zasada, ale nie jest celem samym w sobie. Chodzi o to, aby ukryć szczegóły wewnętrznych implementacji, a nie po to, żeby się nieźle zabawić. –

+0

Tak, nie będę miał żadnych argumentów, jeśli zostanie to nazwane wytyczną Demeter, ale nie ma sensu być nieśmiałym, próbując stado koty doradzać programistom. Nie mam wątpliwości, że jako ćwiczenie akademickie, zastosowanie go jako Prawa jest interesujące i owocne i daje wiele pomysłów na ulepszenie projektowania interfejsu. IMO narysujesz linię, gdy tylko rutynowo osadzasz jeden interfejs w drugim. Czasami X faktycznie ma po prostu Y i wszyscy o tym wiedzą :-) –

+0

Dobrze powiedziane. Moje obawy dotyczące szalonych kotów typowych programistów jest to, że niektórzy mają tendencję do postrzegania dobrych pomysłów jako magicznych. Stereotypowym przykładem jest niedawny absolwent, który czyta książkę GoF o wzorach projektowych, a następnie postanawia zmienić WSZYSTKO w Singleton. –

4

Chodzi o to, aby rozmawiać tylko z najbliższymi przyjaciółmi. Więc nie rób tego ...

var a = new A(); 
var x = a.B.doSomething(); 

Zamiast to zrobić ...

var a = new A(); 
var x = a.doSomething(); // where a.doSomething might call b.doSomething(); 

Ma to zalety, jak rzeczy stają się prostsze dla dzwoniących (Car.Start() versus Car.Engine.Start()), ale otrzymujesz wiele małych metod owijania. Możesz także użyć Mediator pattern, aby złagodzić tego typu "naruszenie".

+0

Nigdy wcześniej nie słyszałem o Prawie Demeter, więc ... ma to sens, że wymagane będą funkcje wygody, które delegują do B, ale czy "B getB() const" jest zabronione? Jeśli tak, dlaczego? –

+0

To jest cała idea prawa ... można to zmienić "Rozmawiasz tylko z najbliższymi przyjaciółmi". Jakie są korzyści? Pomysł ten istnieje od dłuższego czasu. Obecnie oczywiście piszemy bardzo luźno powiązane aplikacje. –

4

Odpowiedź JP jest całkiem niezła, więc jest to tylko dodatek, a nie spór lub inny zamiennik.

Sposób, w jaki rozumiem tę heurystykę, polega na tym, że połączenie z A nie powinno przerwać z powodu zmiany klasy B. Więc jeśli łańcuchujesz połączenia za pomocą a.b.foo(), interfejs A staje się zależny od B, naruszając regułę. Zamiast tego powinieneś wywołać funkcję a.BFoo(), która wywołuje dla ciebie b.foo().

Jest to dobra zasada, ale może to prowadzić do niezręcznego kodu, który tak naprawdę nie rozwiązuje problemu zależnego od niego. Teraz A musi oferować BFoo na zawsze, nawet jeśli B nie oferuje już Foo. Niewielka poprawa i byłaby prawdopodobnie lepsza przynajmniej w niektórych przypadkach, gdyby zmiany w B przerwały dzwoniącego, który chce Foo, a nie samego B.

Dodam jeszcze, że ściśle rzecz biorąc, zasada ta jest zepsuta w sposób ciągły dla pewnej grupy wszechobecnej klasy, takiej jak łańcuch. Być może jest dopuszczalne, aby zdecydować, które klasy są podobnie wszechobecne w ramach danej warstwy aplikacji i swobodnie ignorować "regułę" Demeter dla nich.

+0

Kinda musi dać ci +1, ponieważ powiedziałeś tak samo jak ja tylko krótszy i szybszy :-) –

+0

Amd Oddałem przysługę, ponieważ włączyłeś ładny przykład. –

Powiązane problemy