Na dwa rodzaje metod rozbita klasa: poleceń oraz zapytań.
Postanowiłem poruszyć kwestię zasady oddzielania poleceń od zapytań. Pisał o niej ostatnio Piotr Zieliński. Cóż to takiego te „polecenia” i „zapytania”? Ujmując to zagadnienie bardzo ogólnie można powiedzieć, że cechy charakteryzujące klasę dzielą się na polecenia i zapytania. Pierwsze z nich służą do modyfikowania obiektu, drugie do uzyskiwania o nim informacji. Polecenia są realizowane za pomocą metod nie zwracających rezultatu, zaś zapytania mogą być zarówno metodami z rezultatem (czyli funkcjami obliczającymi wartość), jak również właściwościami (które wartość po prostu przechowują, albo – tak jak funkcje – obliczają).
Pojęcie „polecenie” jednoznacznie określa, wykonanie jakiegoś działania przez obiekt. Konsekwencją takiego działania zazwyczaj będzie jakaś zmiana (np. stanu tego obiektu). Również pojęcie „zapytanie” nie pozostawia wątpliwości, że pytany obiekt, będzie zobligowany do udzielenia (zwrócenia) odpowiedzi. Nie ma tu miejsca na dualizm, który umożliwiałby zrealizowanie obu zadań na raz, tj. udzielenie odpowiedzi i – przy okazji – wykonanie jakiegoś działania. Gdyby zapytanie oprócz zwracania odpowiedzi dokonywało dodatkowych działań zarezerwowanych dla polecenia (czyli dokonywało zmian), to byłby to tzw. skutek uboczny w stosunku do oficjalnego przeznaczenia funkcji. Tu warto wspomnieć o innej zasadzie, którą takie zachowanie by łamało – zasadzie najmniejszego zaskoczenia, która stanowi, że każda metoda lub klasa powinna być implementowana w taki sposób, aby każdy programista mógł się po niej spodziewać właśnie takiej implementacji (czyli powinna działać zgodnie z przeznaczeniem, najczęściej wynikającym z jej nazwy).
W tym momencie może się pojawić następujący argument: skoro metoda ma działać zgodnie z przeznaczeniem to nazwijmy ją odpowiednio, np. Usuń_element_z_kolekcji_i_zwróć_jej_długość, w ten sposób programista używający metody wie czego się po niej spodziewać, a ta załatwia dwie rzeczy na raz. Jak się za chwilę okaże, zasada oddzielania poleceń od zapytań, nie zabrania utworzenia takiej metody, o ile zostaną spełnione wszystkie wymogi tejże zasady.
Paradoks? Nie, zwróćmy uwagę, że ta zasada wymaga, aby klasa miała pełen zestaw poleceń, które pozwolą nią manipulować w stu procentach i pełen zestaw zapytań, aby żadna informacja, która jest niezbędna do prawidłowego sterowania obiektem nie została zatajona. Zatem jak najbardziej można stworzyć powyższą metodę, o ile oprócz niej będą istniały dwie metody: polecenia i zapytania, pierwsza usuwająca element, a druga pozwalająca dowiedzieć się, jaka jest obecnie długość kolekcji i obie zostaną użyte przez powyższą metodę (zgodnie z kolejną zasadą DRY – Don’t Repat Yourself – nie powtarzaj się). Jeśli takich metod nie stworzymy, realizując jedynie dyskutowaną metodę, uniemożliwimy ustalanie stanu obiektu (żeby określić długość kolekcji będziemy musieli coś do niej dodać, a następnie użyć tej metody, aby poznać długość).
Warto zauważyć, że opisywana zasada jest de facto próbą zagwarantowania deterministycznej postaci obiektu. Jeśli bowiem zapytania będą wpływały na odpowiedź, determinizm nie będzie miał miejsca. Przykładem takiego braku deterministycznej postaci, może być szeroko stosowana w przeszłości funkcja zwracająca znak ze standardowego wejścia, przy jednoczesnym przesunięciu wskaźnika odczytu na kolejny znak. W ten sposób nie było możliwe odczytanie tego samego znaku przez inny moduł oprogramowania, niż ten obecnie korzystający ze standardowego wejścia.
Dotychczasowe rozważania to jednak nie wszystko, co należy uwypuklić w tej zasadzie. Istotną kwestią jest sam zakres separacji. Otóż nie musi on dotyczyć (choć może) tych elementów klasy, które nie są udostępniane. To do czego dowolni klienci klasy nie uzyskają dostępu, może się do tej zasady nie stosować (czyli metody prywatne mogą sobie hasać do woli ;)).
Opisane w powyższym akapicie fakty wskazują na ostatnią kwestię, którą należy rozpatrzyć w zasadzie separacji. Jest nią pojęcie skutku ubocznego, który jest działaniem, mającym wpływ wyłącznie na zapytania udostępniane przez klasę jej klientom. Dopuszczalne są więc takie zapytania, które ze względu na realizowane zadanie, podczas swojego działania dokonają czasowej zmiany stanu obiektu, przywracając go jednakże na koniec do stanu zastanego. Przykładem mogą być funkcje minimum i maksimum, które muszą przeszukać kolekcję w celu odnalezienia jej minimalnej bądź maksymalnej wartości. W czasie ich działania zmienia się indeks bieżącego elementu kolekcji, ale z punktu widzenia klienta po zwróceniu odpowiedzi będzie on taki, jak przed zadaniem pytania.
Na koniec chciałbym wrócić do pewnych fragmentów z publikacji Piotra Zielińskiego, które miałyby wskazywać na ciemne strony zasady separacji, zmieniające ją w anty-zasadę.
We wpisie można m.in. przeczytać: „…pierwszy przykład to programowanie współbieżne, gdzie należy unikać częstych lock’ow i z tego względu lepiej skorzystać z bardziej rozbudowanej ale jednej metody…”. Nie bardzo widzę tutaj sprzeczność z zasadą separacji. W żadnym razie nie wymaga ona, aby zapytanie nie było rozbudowane. Wymaga jedynie, aby było wyłącznie zapytaniem. Jeśli jest tak, że ze względu na współbieżność, trzeba w jednej, atomowej operacji dokonać wielu działań, to tak jak pisałem w kontekście przykładowej metody Usuń_element_z_kolekcji_i_zwróć_jej_długość, możliwe jest zebranie kilku poleceń oraz zapytań i wykonanie ich w ramach jednego przebiegu. Nie można jedynie sprawić, aby taka multimetoda, realizowała wszystko samodzielnie, odzierając klasę z oczekiwanych poleceń i zapytań. W przypadku współbieżności problemem tak naprawdę jest zapewnienie wyłącznego dostępu, a ten jest bardzo prosty do zrealizowania – przecież dysponujemy klasą i jej hermetycznymi cechami. Wspomniana multimetoda musi jedynie aktywować na początku swego działania wyłączny dostęp i wewnętrzny znacznik, mówiący o tym, że wyłączność aktywowała (powinna to realizować dedykowaną, zapewne prywatną, metodą-poleceniem), a na końcu ją dezaktywować (także dedykowaną metodą). Te same metody aktywacji i dezaktywacji powinny być używana przez zapytania i polecenia, które wymagają wyłącznego dostępu (a są używane przez multimetodę). Jednakże uruchomienie ich w tych elementach klasy, w przypadku kiedy wyłączność już działa, nie powinno przynosić skutku, a jedynie odpowiednio zwiększać lub zmniejszać licznik aktywacji, co pozwoli koniec końców wyłączność dezaktywować.
Kolejny fragment, do którego chciałbym się odnieść, to: „…tworząc usługę sieciową lepiej aby jedno zapytanie zrobiło tyle co trzeba zamiast wysyłać kilka pojedynczych zapytań – interfejs warstwy usług nie powinien być ‚chatty’…”. W tym wypadku także nie bardzo rozumiem, w czym przeszkadza zasada separacji? Nie wymaga ona, aby zapytanie było jak najkrótsze, nie wymaga, aby odpowiadało dokładnie jednemu zapytaniu do warstwy usług. Wymaga jedynie, aby było tylko i wyłącznie zapytaniem, które zwróci informację o obiekcie. Może być więc tak, że zostanie wykonana cała masa zapytań do warstwy usług, które przełożą się na wartości nie tylko tej metody-zapytania, ale i innych metod-zapytań obiektu (taki ciąg zapytań powinna realizować prywatna metoda obiektu odpytującą warstwę, która – jak wspominałem wcześniej – nie musi się stosować do zasady separacji, choć w tym wypadku, zastosowanie się do niej niczego w jej działaniu nie zmieni). Wynik zostaną zbuforowane i wywołanie metod-zapytań dotyczących tych wyników, nie spowoduje już ponownego dręczenia warstwy usług (co ograniczy jej gadatliwość), ale wykorzystanie tych zbuforowanych.
Piotr wspomina także o metodzie POP – zdejmującej obiekt ze stosu, ale tu znowu mamy przypadek multimetody. Dopóki będzie zapytanie zwracające obiekt ze szczytu stosu (bez jego usuwania) i polecenie, które ten obiekt usuwa (bez jego zwracania), może sobie istnieć POP. Bez tych dwóch metod POP istnieć nie może.