Zasada Tell Don't Ask mówi:
powinieneś starać się powiedzieć obiektom, co chcesz, aby zrobiły; nie zadawaj im pytań o ich stan, podejmuj decyzje, a następnie mów im, co mają robić.
Problem polega na tym, że jako osoba wywołująca nie powinna podejmować decyzji opartych na stanie wywoływanego obiektu, które powodują zmianę stanu obiektu. Logika, którą wdrażasz, jest prawdopodobnie odpowiedzialnością wywoływanego obiektu, a nie twoją. Podejmowanie decyzji poza obiektem narusza jego enkapsulację.
Prostym przykładem „Powiedz, nie pytaj” jest
Widget w = ...;
if (w.getParent() != null) {
Panel parent = w.getParent();
parent.remove(w);
}
a wersja tell to ...
Widget w = ...;
w.removeFromParent();
Ale co jeśli muszę znać wynik metody removeFromParent? Moja pierwsza reakcja polegała na zmianie metody removeFromParent na zwrócenie wartości logicznej oznaczającej, czy element nadrzędny został usunięty, czy nie.
Ale potem natknąłem się na wzorzec rozdzielania zapytań, który mówi NIE robić tego.
Stwierdza, że każda metoda powinna być albo komendą wykonującą akcję, albo zapytaniem zwracającym dane do dzwoniącego, ale nie jednym i drugim. Innymi słowy, zadanie pytania nie powinno zmienić odpowiedzi. Bardziej formalnie, metody powinny zwracać wartość tylko wtedy, gdy są referencyjnie przejrzyste, a zatem nie mają żadnych skutków ubocznych.
Czy te dwie rzeczy naprawdę są ze sobą sprzeczne i jak wybrać między nimi? Czy korzystam z tego w Pragmatic Programmer lub Bertrand Meyer?
Odpowiedzi:
W rzeczywistości twój przykładowy problem ilustruje już brak rozkładu.
Zmieńmy to trochę:
Tak naprawdę nie jest inaczej, ale uwidacznia się wada: dlaczego książka miałaby wiedzieć o swojej półce? Mówiąc wprost, nie powinno. Wprowadza zależność książek na półkach (co nie ma sensu) i tworzy okrągłe odniesienia. Wszystko jest źle.
Podobnie widżet nie musi znać swojego elementu nadrzędnego. Powiesz: „Ok, ale widżet potrzebuje swojego elementu nadrzędnego, aby sam układ się poprawnie itp.” i pod maską widżet zna swojego rodzica i prosi go o obliczenia własnych metryk na ich podstawie itp. Mówiąc, nie pytaj, że to źle. Rodzic powinien powiedzieć wszystkim swoim dzieciom, aby renderowały, przekazując wszystkie niezbędne informacje jako argumenty. W ten sposób można łatwo mieć ten sam widget w dwóch rodzicach (niezależnie od tego, czy ma to sens).
Aby wrócić do przykładu książki - wpisz bibliotekarza:
Proszę zrozumieć, że nie pytamy już w sensie powiedzieć, nie pytaj .
W pierwszej wersji półka była własnością książki i poprosiłeś o nią. W drugiej wersji nie zakładamy takiego założenia na temat książki. Wiemy tylko, że bibliotekarz może nam powiedzieć półkę z książką. Przypuszczalnie opiera się na jakimś rodzaju tabeli przeglądowej, ale nie jesteśmy tego pewni (mógł po prostu przeglądać wszystkie książki na każdej półce) i tak naprawdę nie chcemy tego wiedzieć .
Nie polegamy na tym, czy lub jak odpowiedź bibliotekarzy jest sprzężona z jej stanem lub ze stanem innych obiektów, od których może zależeć. Mówimy bibliotekarzowi, żeby znalazł półkę.
W pierwszym scenariuszu bezpośrednio zakodowaliśmy relację między książką a półką jako część stanu książki i pobraliśmy ten stan bezpośrednio. Jest to bardzo kruche, ponieważ przyjmujemy również założenie, że półka zwrócona przez książkę zawiera książkę (jest to ograniczenie, które musimy zapewnić, w przeciwnym razie możemy nie być w stanie wydać
remove
książki z półki, w której się znajduje).Wraz z wprowadzeniem bibliotekarza modelujemy ten związek osobno, osiągając w ten sposób rozdzielenie obaw.
źródło
Jeśli potrzebujesz znać wynik, to robisz; to twoje wymaganie.
Wyrażenie „metody powinny zwracać wartość tylko wtedy, gdy są referencyjnie przezroczyste, a zatem nie mają skutków ubocznych” jest dobrą wskazówką do naśladowania (szczególnie jeśli piszesz swój program w funkcjonalnym stylu, ze względu na współbieżność lub z innych powodów), ale jest to nie absolut.
Na przykład możesz potrzebować znać wynik operacji zapisu pliku (prawda lub fałsz). To przykład metody, która zwraca wartość, ale zawsze wywołuje skutki uboczne; nie można tego obejść.
Aby wykonać rozdzielanie poleceń / kwerend, należy wykonać operację na pliku za pomocą jednej metody, a następnie sprawdzić jej wynik za pomocą innej metody, co jest niepożądaną techniką, ponieważ oddziela wynik od metody, która spowodowała wynik. Co się stanie, jeśli coś stanie się ze stanem obiektu między wywołaniem pliku a sprawdzeniem stanu?
Najważniejsze jest to: jeśli używasz wyniku wywołania metody do podejmowania decyzji w innym miejscu w programie, nie naruszasz zasad Tell Don't Ask. Jeśli natomiast podejmujesz decyzje dotyczące obiektu na podstawie wywołania metody do tego obiektu, powinieneś przenieść te decyzje do samego obiektu, aby zachować enkapsulację.
Nie jest to wcale sprzeczne z separacją zapytań dowodzenia; w rzeczywistości dodatkowo to egzekwuje, ponieważ nie ma już potrzeby ujawniania zewnętrznej metody dla celów statusu.
źródło
Myślę, że powinieneś iść ze swoim początkowym instynktem. Czasami projekt powinien być celowo niebezpieczny, aby wprowadzić złożoność natychmiast po komunikowaniu się z przedmiotem, aby wymuszone obchodzenie się z nim właściwie od razu, zamiast próbować obsługiwać go na samym obiekcie i ukryć wszystkie krwawe szczegóły i utrudnić czyste zmienić podstawowe założenia.
Powinieneś poczuć tonięcie, jeśli obiekt oferuje zaimplementowane odpowiedniki
open
iclose
zachowania, i natychmiast zaczynasz rozumieć, z czym masz do czynienia, gdy zobaczysz wartość logiczną zwrotu dla czegoś, co Twoim zdaniem byłoby prostym zadaniem atomowym.Jak sobie z tym poradzisz, to twoja sprawa. Możesz utworzyć nad nim abstrakcję, typ widgetu
removeFromParent()
, ale zawsze powinieneś mieć rezerwę na niskim poziomie, w przeciwnym razie prawdopodobnie przyjmujesz przedwczesne założenia.Nie staraj się, aby wszystko było proste. Nie ma nic bardziej rozczarowującego, gdy polegasz na czymś, co wydaje się być eleganckie i tak niewinne, tylko po to, aby uświadomić sobie, że to prawdziwy horror w najgorszym momencie.
źródło
To, co opisujesz, jest znanym „wyjątkiem” od zasady rozdzielania zapytań i poleceń.
W tym artykule Martin Fowler wyjaśnia, jak zagroziłby takiemu wyjątkowi.
W twoim przykładzie rozważę ten sam wyjątek.
źródło
Rozdzielenie poleceń i zapytań jest niezwykle łatwe do niezrozumienia.
Jeśli powiem ci w moim systemie, istnieje polecenie, które również zwraca wartość z zapytania i mówisz: „Ha! Naruszasz!” skaczesz z pistoletu.
Nie, to nie jest zabronione.
Zabronione jest, gdy to polecenie jest JEDYNYM sposobem wykonania tego zapytania. Nie. Nie powinienem zmieniać stanu, aby zadawać pytania. To nie znaczy, że muszę zamykać oczy za każdym razem, gdy zmieniam stan.
Nie ma znaczenia, czy to zrobię, ponieważ i tak zamierzam je ponownie otworzyć. Jedyną korzyścią z tej nadmiernej reakcji było to, że projektanci nie byli leniwi i nie zapomnieli dołączyć zapytania bez zmiany stanu.
Prawdziwe znaczenie jest tak cholernie subtelne, że leniwym ludziom łatwiej jest powiedzieć, że rozgrywający powinni powrócić. Ułatwia to upewnienie się, że nie naruszasz prawdziwej reguły, ale jest to nadmierna reakcja, która może stać się prawdziwym bólem.
Płynne interfejsy i iDSL ciągle „naruszają” tę nadmierną reakcję. Jeśli zareagujesz nadmiernie, zignorujesz wiele mocy.
Możesz argumentować, że polecenie powinno robić tylko jedną rzecz, a zapytanie powinno robić tylko jedną rzecz. I w wielu przypadkach jest to dobry punkt. Kto powiedział, że jest tylko jedno zapytanie, które powinno być zgodne z poleceniem? Dobrze, ale to nie jest separacja poleceń i zapytań. To zasada pojedynczej odpowiedzialności.
Patrząc w ten sposób, pop stos nie jest jakimś dziwnym wyjątkiem. To jedna odpowiedzialność. Pop narusza separację zapytań, jeśli zapomnisz podać podgląd.
źródło