Ten blog został opublikowany w Hacker News z kilkoma pozytywnymi opiniami. Pochodząc z C ++, większość tych przykładów wydaje się sprzeczna z tym, czego mnie nauczono.
Tak jak w przykładzie 2:
Zły:
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
kontra dobry:
system_monitor.check_for_overheating
class SystemMonitor
def check_for_overheating
if temperature > 100
sound_alarms
end
end
end
Rada w C ++ jest taka, że powinieneś preferować wolne funkcje zamiast funkcji składowych, ponieważ zwiększają one enkapsulację. Oba są identyczne semantycznie, więc dlaczego preferować wybór, który ma dostęp do większej liczby stanów?
Przykład 4:
Zły:
def street_name(user)
if user.address
user.address.street_name
else
'No street name on file'
end
end
kontra dobry:
def street_name(user)
user.address.street_name
end
class User
def address
@address || NullAddress.new
end
end
class NullAddress
def street_name
'No street name on file'
end
end
Dlaczego User
formatowanie niepowiązanego ciągu błędów jest obowiązkiem ? Co jeśli chcę zrobić coś poza drukowaniem, 'No street name on file'
jeśli nie ma ulicy? Co jeśli ulica ma taką samą nazwę?
Czy ktoś mógłby mnie oświecić na temat zalet i uzasadnienia „Powiedz, nie pytaj”? Nie szukam tego, co jest lepsze, ale próbuję zrozumieć punkt widzenia autora.
źródło
Odpowiedzi:
Pytanie obiektu o jego stan, a następnie wywołanie metod na tym obiekcie na podstawie decyzji podejmowanych poza nim oznacza, że obiekt jest teraz nieszczelną abstrakcją; niektóre z jego zachowań znajdują się poza obiektem, a stan wewnętrzny jest narażony (być może niepotrzebnie) na świat zewnętrzny.
http://pragprog.com/articles/tell-dont-ask
źródło
Ogólnie rzecz biorąc, artykuł sugeruje, że nie powinieneś narażać państwa członkowskiego na przemawianie przez innych, jeśli możesz sam o tym myśleć .
Jednak nie jest jednoznacznie stwierdzone, że prawo to mieści się w bardzo oczywistych granicach, gdy rozumowanie wykracza poza zakres odpowiedzialności określonej klasy. Na przykład, każda klasa, której zadaniem jest utrzymanie pewnej wartości lub dostarczenie pewnej wartości - szczególnie ogólnej, lub w której klasa zapewnia zachowanie, które należy rozszerzyć.
Na przykład, jeśli system podaje
temperature
zapytanie, to jutro klient może to zrobićcheck_for_underheating
bez konieczności zmianySystemMonitor
. Nie dzieje się tak w przypadku samychSystemMonitor
narzędzicheck_for_overheating
. ZatemSystemMonitor
klasa, której zadaniem jest wzbudzanie alarmu, gdy temperatura jest zbyt wysoka, postępuje zgodnie z tym - aleSystemMonitor
klasa, której zadaniem jest umożliwienie innemu kodowi odczytania temperatury, aby mógł kontrolować, powiedzmy, TurboBoost lub coś w tym rodzaju , nie powinieneś.Zauważ również, że drugi przykład bezcelowo wykorzystuje anty-wzorzec Null Object.
źródło
check_for_underheating
bez konieczności zmianySystemMonitor
”. Czym różni się klient odSystemMonitor
tego momentu? Czy nie rozpraszasz teraz logiki monitorowania na wiele klas? Nie widzę też problemu z klasą monitora, która dostarcza informacje z czujnika innym klasom, jednocześnie rezerwując sobie funkcje alarmowe. Kontroler doładowania powinien sterować doładowaniem bez konieczności martwienia się o podniesienie alarmu, jeśli temperatura będzie zbyt wysoka.Prawdziwy problem z twoim przykładem przegrzania polega na tym, że reguły dotyczące tego, co kwalifikuje się jako przegrzanie, nie są łatwo zmieniane dla różnych systemów. Załóżmy, że system A jest taki, jaki masz (temperatura> 100 się przegrzewa), ale system B jest bardziej delikatny (temperatura> 93 się przegrzewa). Czy zmieniasz funkcję sterowania, aby sprawdzić typ systemu, a następnie zastosujesz prawidłową wartość?
Czy może każdy typ systemu określa jego moc grzewczą?
EDYTOWAĆ:
Ten pierwszy sposób sprawia, że funkcja kontrolna staje się brzydka, gdy zaczynasz radzić sobie z większą liczbą systemów. Ta ostatnia pozwala na stabilne działanie funkcji sterowania w miarę upływu czasu.
źródło
Po pierwsze, czuję, że muszę zrobić wyjątek od twojej charakterystyki przykładów jako „złych” i „dobrych”. W artykule użyto terminów „Nie tak dobrze” i „Lepiej”. Sądzę, że zostały one wybrane z jakiegoś powodu: są to wytyczne i zależnie od okoliczności podejście „Nie tak dobrze” może być właściwe, a nawet jedyne rozwiązanie.
Kiedy masz wybór, powinieneś preferować włączenie jakiejkolwiek funkcjonalności, która opiera się wyłącznie na klasie w klasie, a nie poza nią - przyczyną jest hermetyzacja i fakt, że ułatwia to ewolucję klasy w czasie. Klasa lepiej radzi sobie także z reklamowaniem swoich możliwości niż kilka bezpłatnych funkcji.
Czasami musisz powiedzieć, ponieważ decyzja zależy od czegoś spoza klasy lub ponieważ jest to po prostu coś, czego nie chcesz robić od większości użytkowników klasy. Czasami chcesz powiedzieć, ponieważ zachowanie jest sprzeczne z intuicją dla klasy i nie chcesz mylić większości użytkowników klasy.
Na przykład narzekasz na to, że adres ulicy zwraca komunikat o błędzie, to nie jest to, co robi, to podać wartość domyślną. Ale czasami wartość domyślna jest nieodpowiednia. Jeśli był to stan lub miasto, możesz chcieć przyjąć wartość domyślną podczas przypisywania rekordu sprzedawcy lub ankiecie, aby wszystkie niewiadome trafiły do konkretnej osoby. Z drugiej strony, jeśli drukujesz koperty, możesz preferować wyjątek lub ochronę, która nie pozwala marnować papieru na listy, których nie można dostarczyć.
Mogą się zdarzyć przypadki, w których „Nie tak dobrze” jest dobrym rozwiązaniem, ale ogólnie „Lepsze” jest, cóż, lepsze.
źródło
Antymetria danych / obiektów
Jak zauważyli inni, Tell-Dont-Ask jest szczególnie przeznaczony do przypadków, w których zmieniasz stan obiektu po zapytaniu (patrz np. Tekst Pragprog opublikowany gdzie indziej na tej stronie). Nie zawsze tak jest, np. Obiekt „użytkownik” nie jest zmieniany po zapytaniu o adres user.address. Dlatego jest dyskusyjne, czy jest to odpowiedni przypadek zastosowania Tell-Dont-Ask.
Tell-Dont-Ask zajmuje się odpowiedzialnością, a nie wyciąganiem logiki z klasy, która powinna być w niej uzasadniona. Ale nie cała logika, która dotyczy obiektów, musi być logiką tych obiektów. Jest to wskazówka na głębszym poziomie, nawet poza Tell-Dont-Ask, i chcę dodać krótką uwagę na ten temat.
W ramach projektu architektonicznego możesz chcieć mieć obiekty, które są tak naprawdę tylko kontenerami dla właściwości, a może nawet niezmiennymi, a następnie uruchamiać różne funkcje nad kolekcjami takich obiektów, oceniając, filtrując lub przekształcając je zamiast wysyłać im polecenia (co jest więcej domeny Tell-Dont-Ask).
Wybór bardziej odpowiedni dla twojego problemu zależy od tego, czy spodziewasz się stabilnych danych (obiektów deklaratywnych), ale ze zmianą / dodaniem po stronie funkcji. Lub jeśli oczekujesz stabilnego i ograniczonego zestawu takich funkcji, ale oczekujesz większego strumienia na poziomie obiektów, np. Poprzez dodanie nowych typów. W pierwszej sytuacji wolisz funkcje wolne, w metodach drugiego obiektu.
Bob Martin w swojej książce „Clean Code” nazywa to „antysymetrią danych / obiektów” (s. 95ff), inne społeczności mogą nazywać to „ problemem ekspresji ”.
źródło
Ten paradygmat jest czasem określany jako „powiedz, nie pytaj” , co oznacza, że powiedz obiektowi, co ma robić, nie pytaj o jego stan; a czasem jako „Zapytaj, nie mów” , co oznacza, że poproś obiekt, aby coś dla ciebie zrobił, nie mów mu, jaki powinien być jego stan. W obu przypadkach najlepsza praktyka jest taka sama - sposób, w jaki obiekt powinien wykonać akcję, dotyczy troski o obiekt, a nie obiektu wywołującego. Interfejsy powinny unikać ujawniania swojego stanu (np. Poprzez akcesoria lub właściwości publiczne), a zamiast tego ujawniać metody „robienia”, których implementacja jest nieprzejrzysta. Inni opisali to linkami do pragmatycznego programisty.
Ta reguła jest związana z regułą dotyczącą unikania kodu „podwójnej kropki” lub „podwójnej strzałki”, często nazywanego „Rozmawiaj tylko z najbliższymi przyjaciółmi”, który
foo->getBar()->doSomething()
to stan jest zły, zamiast tego użyj funkcji,foo->doSomething();
która jest funkcją zawijania opakowania wokół paska, i wdrożony jako prostyreturn bar->doSomething();
- jeślifoo
jest odpowiedzialny za zarządzaniebar
, niech to zrobi!źródło
Oprócz innych dobrych odpowiedzi na temat „mów, nie pytaj”, komentarz do konkretnych przykładów, który może pomóc:
Ten wybór nie ma dostępu do więcej stanu. Obaj używają tej samej ilości stanu do wykonywania swoich zadań, ale „zły” przykład wymaga, aby państwo klasy było publiczne, aby wykonywać swoją pracę. Ponadto zachowanie tej klasy w „złym” przykładzie rozciąga się na funkcję bezpłatną, co utrudnia jej znalezienie i utrudnia refaktoryzację.
Dlaczego „nazwa ulicy” jest obowiązkiem zarówno „uzyskać nazwę ulicy”, jak i „podać komunikat o błędzie”? Przynajmniej w „dobrej” wersji każdy kawałek ma jedną odpowiedzialność. Mimo to nie jest to świetny przykład.
źródło
Te odpowiedzi są bardzo dobre, ale oto kolejny przykład, aby podkreślić: pamiętaj, że zwykle jest to sposób na uniknięcie powielania. Załóżmy na przykład, że masz kilka miejsc z kodem takim jak:
Oznacza to, że lepiej mieć coś takiego:
Ponieważ to duplikowanie oznacza, że większość klientów interfejsu użyłaby nowej metody, zamiast powtarzać tę samą logikę tu i tam. Dajesz swojemu delegatowi pracę, którą chcesz wykonać, zamiast prosić o informacje potrzebne do zrobienia tego sam.
źródło
Uważam, że jest to bardziej prawdziwe przy pisaniu obiektów wysokiego poziomu, ale mniej prawdziwe przy schodzeniu do głębszego poziomu, np. Biblioteki klas, ponieważ niemożliwe jest napisanie każdej metody, aby zadowolić wszystkich konsumentów klas.
Na przykład # 2, myślę, że jest zbyt uproszczony. Gdybyśmy rzeczywiście zamierzali to zaimplementować, SystemMonitor miałby w końcu kod dla niskiego poziomu dostępu do sprzętu i logiki dla abstrakcji na wysokim poziomie osadzony w tej samej klasie. Niestety, jeśli spróbujemy podzielić to na dwie klasy, naruszylibyśmy samo „Powiedz, nie pytaj”.
Przykład nr 4 jest mniej więcej taki sam - osadza logikę interfejsu użytkownika w warstwie danych. Teraz, jeśli chcemy naprawić to, co użytkownik chce zobaczyć w przypadku braku adresu, musimy naprawić obiekt w warstwie danych, a co jeśli dwa projekty używające tego samego obiektu, ale muszą użyć innego tekstu dla adresu zerowego?
Zgadzam się, że gdybyśmy mogli wdrożyć „Powiedz, nie pytaj” o wszystko, byłoby to bardzo przydatne - sam byłbym szczęśliwy, gdybym mógł raczej powiedzieć niż poprosić (i zrobić to sam) w prawdziwym życiu! Jednak, podobnie jak w prawdziwym życiu, wykonalność rozwiązania jest bardzo ograniczona do klas wysokiego poziomu.
źródło