Czytam książkę zatytułowaną Rails AntiPatterns, a oni mówią o korzystaniu z delegacji, aby uniknąć łamania Prawa Demeter. Oto ich najlepszy przykład:
Uważają, że wywołanie czegoś takiego w kontrolerze jest złe (i zgadzam się)
@street = @invoice.customer.address.street
Ich proponowanym rozwiązaniem jest wykonanie następujących czynności:
class Customer
has_one :address
belongs_to :invoice
def street
address.street
end
end
class Invoice
has_one :customer
def customer_street
customer.street
end
end
@street = @invoice.customer_street
Mówią, że skoro używasz tylko jednej kropki, nie łamiesz tutaj Prawa Demetera. Myślę, że jest to niepoprawne, ponieważ nadal przechodzisz przez klienta, aby przejść przez adres, aby uzyskać ulicę z fakturą. Ten pomysł pochodzi przede wszystkim z postu na blogu, który przeczytałem:
http://www.dan-manges.com/blog/37
W poście na blogu najlepszym przykładem jest
class Wallet
attr_accessor :cash
end
class Customer
has_one :wallet
# attribute delegation
def cash
@wallet.cash
end
end
class Paperboy
def collect_money(customer, due_amount)
if customer.cash < due_ammount
raise InsufficientFundsError
else
customer.cash -= due_amount
@collected_amount += due_amount
end
end
end
Stany wpis na blogu, że chociaż istnieje tylko jedna kropka customer.cash
zamiast customer.wallet.cash
tego kodu nadal narusza Prawo Demeter.
Teraz w metodzie Paperboy Collect_money nie mamy dwóch kropek, mamy tylko jedną w „customer.cash”. Czy ta delegacja rozwiązała nasz problem? Ani trochę. Jeśli spojrzymy na to zachowanie, gazeciarz wciąż sięga bezpośrednio do portfela klienta, aby uzyskać gotówkę.
EDYTOWAĆ
W pełni rozumiem i zgadzam się, że to nadal stanowi naruszenie i muszę stworzyć metodę Wallet
zwaną wypłatą, która obsłuży dla mnie płatność i że powinienem wywołać tę metodę w Customer
klasie. Nie rozumiem, że zgodnie z tym procesem mój pierwszy przykład wciąż narusza Prawo Demetera, ponieważ Invoice
wciąż sięga bezpośrednio Customer
po ulicę.
Czy ktoś może mi pomóc usunąć zamieszanie. Przez ostatnie 2 dni szukałem sposobu, aby ten temat zagłębił się, ale nadal jest mylący.
źródło
Odpowiedzi:
Twój pierwszy przykład nie narusza prawa Demeter. Tak, przy obecnym kodzie mówiąc
@invoice.customer_street
, zdarza się, że dostaje taką samą wartość , jak hipotetyczna@invoice.customer.address.street
, ale na każdym etapie przejścia o wartości zwracanej decyduje pytany obiekt - nie chodzi o to, że „gazeciarz sięga do portfel klienta ”, to znaczy, że„ gazeciarz prosi klienta o gotówkę, a klient otrzymuje gotówkę z portfela ”.Kiedy mówisz
@invoice.customer.address.street
, jesteś przy założeniu znajomości klienta i adres wewnętrznych - to jest złe. Kiedy mówisz@invoice.customer_street
, pytasz:invoice
„hej, chciałbym, żeby ulica klienta, ty decydujesz, jak ją zdobyć ”. Następnie klient mówi na swój adres: „hej, chciałbym, żeby twoja ulica decydowała, jak ją zdobyć ”.Nacisk Demetera nie polega na tym , że „nigdy nie możesz poznać wartości z obiektów znajdujących się daleko na wykresie od ciebie”, lecz „ samemu nie wolno przechodzić daleko wzdłuż wykresu obiektowego w celu uzyskania wartości”.
Zgadzam się, że może to wydawać się subtelnym rozróżnieniem, ale rozważ to: w kodzie zgodnym z Demeter, ile kodu musi się zmienić, gdy wewnętrzna reprezentacja
address
zmian? Co z kodem niezgodnym z Demeter?źródło
Pierwszy przykład i drugi nie są właściwie takie same. Podczas gdy pierwsza mówi o ogólnych zasadach „jednej kropki”, druga mówi więcej o innych rzeczach w projektowaniu OO, szczególnie „ Mów, nie pytaj ”
Znowu „ tylko dla zachowania, a nie dla atrybutów ”
Jeśli poprosisz o atrybuty, powinieneś zapytać . „Hej, koleś, ile masz pieniędzy w kieszeni? Pokaż, ocenię, czy możesz to zapłacić”. To nie tak, żaden sprzedawca nie będzie się tak zachowywał. Zamiast tego powiedzą: „Proszę zapłacić”
Obowiązkiem klienta będzie ocena, czy powinien zapłacić i czy może zapłacić. Zadanie urzędnika kończy się po wezwaniu klienta do zapłaty.
Czy zatem drugi przykład dowodzi, że pierwszy jest błędny?
W mojej opinii. Nie , o ile:
1. Robisz to z samoograniczeniem.
Chociaż możesz uzyskać dostęp do wszystkich atrybutów klienta w
@invoice
drodze delegacji, rzadko jest to potrzebne w normalnych przypadkach.Pomyśl o stronie przedstawiającej fakturę w aplikacji Rails. Na górze będzie sekcja pokazująca dane klienta. Czy w szablonie faktury kodujesz w ten sposób?
To źle i nieefektywnie. Lepszym podejściem jest
Następnie pozwól klientowi częściowo przetworzyć wszystkie atrybuty należące do klienta.
Więc generalnie nie potrzebujesz tego. Ale możesz mieć stronę z listą wszystkich ostatnich faktur, w każdej znajduje się pole informacyjne
li
z nazwą klienta. W takim przypadku potrzebujesz atrybutu klienta, aby wyświetlić, i całkowicie kodować szablon jako2. W zależności od tego wywołania metody nie ma dalszych działań.
W powyższym przypadku strony z listą faktura wymagała atrybutu nazwy klienta, ale jej prawdziwym celem jest „ pokaż mi swoje imię ”, więc zasadniczo jest to zachowanie, ale nie atrybut . Nie ma dalszej oceny i działań opartych na tym atrybucie, np. Jeśli masz na imię „Mike”, polubię cię i dam ci 30 dni więcej uznania. Nie, faktura mówi tylko „pokaż mi swoje imię”, nigdy więcej. Jest to więc całkowicie dopuszczalne zgodnie z regułą „Tell Don't Ask” w przykładzie 2.
źródło
Czytaj dalej w drugim artykule i myślę, że pomysł stanie się jaśniejszy. Pomysł polega na zaoferowaniu klientowi możliwości zapłaty i całkowitego ukrycia miejsca przechowywania. Czy to pole, członek portfela czy coś innego? Dzwoniący nie wie, nie musi wiedzieć i nie zmienia się, jeśli zmieniają się szczegóły implementacji.
Myślę więc, że twoje drugie odniesienie daje bardziej pomocne rekomendacje.
Pomysł „jedna kropka” jest częściowym sukcesem, ponieważ kryje w sobie głęboki szczegół, ale wciąż zwiększa sprzężenie między oddzielnymi komponentami.
źródło
Wygląda na to, że Dan wyciągnął swój przykład z tego artykułu: Paperboy, The Wallet i The Law Of Demeter
Rozumiem, że twój przykład jest w języku Ruby, ale powinien mieć zastosowanie we wszystkich językach OOP.
źródło