Ekstremalna obiektowość w praktyce – część 4 – Używaj tylko jednej kropki na linię
Część 0
Część 1
Część 2
Część 3
Słuchając Manowar dzielnie zagłębiamy się w czwartą z zasad Jeff’a Bay’a.
Używaj tylko jednej kropki na linię
Jeżeli przejrzymy dowolny kod pisany „na szybko” będziemy wstanie znaleźć tego typu potworki jak ten tutaj:
Listing 1. Typowy „łańcuszek” w kodzie
package pl.koziolekweb.eowp4;
public class App {
public static void main(String[] args) {
Person person = new Person();
person.name ="Ilona";
System.out.println(isFemaleName(person));
}
public static boolean isFemaleName(Person person) {
return person.name.lastIndexOf("a") == person.name.length() - 1;
}
}
class Person {
String name;
}
Mamy tu do czynienia z typowym „łańcuszkiem” wywołań metod. Kod ten będzie działał w oparciu o wiarę programisty w to, że po drodze nic się nie stanie.
Innym często spotykanym rozwiązaniem związanym z obsługą XMLa jest taki oto potworek:
Listing 2. Następny „łańcuszek” tu obsługa XML.
Element newDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().getDocumentElement();
Dlaczego jest to złe?
Wraz z budową tego typu konstrukcji pojawiają się też błędy. Najpopularniejszym jest klasyczny NullPointerException, który występuje „gdzieś w linii numer”. W takim przypadku zaczyna się walka z debuggrem albo dupa-debug i rozpisywanie kolejnych „kropek” na pojedyncze zmienne. Tak jest zazwyczaj w drugim przedstawionym przypadku.
Ze znacznie poważniejszym problemem mamy do czynienia w pierwszym przykładzie. Pomijając już to co pisałem w poprzedniej części na temat opakowywania typów prostych i stringów to mamy tu jeszcze bonus w postaci zignorowania podstawowych zasad programowania obiektowego. W tamtym przykładzie brakuje najzwyczajniej enkapsulacji. Wprowadzenie gettera nic nie da, bo „goły” getter służy tylko poprawie samopoczucia programisty, ale nie zapewnia enkapsulacji.
Dlaczego jest to takie ważne? Ponieważ jeżeli olejemy w Javie zasady programowania obiektowego to równie dobrze można przesiąść się na COBOLa.
Jak temu zaradzić
Najprościej jest zastosować Prawo DemeterW. Jest to jednak dość ryzykowna zabawa. Przede wszystkim jeżeli mamy kiepsko przemyślaną strukturę to łańcuszek kropek zostanie zastąpiony przez łańcuszek metod. Co ciekawe kod wykazuje tu zadziwiająca właściwość do składowania tych metod jako publiczne w ramach obiektu, w którym mieliśmy łańcuszek. Jeff Bay proponuje więc trochę lżejszą wersję tego prawa. Mówi ona, że możesz:
- Bawić się własnymi zabawkami – legalne jest „łańcuszkowanie” metod prywatnych.
- Bawić się zabawkami, które skonstruujesz – legalne jest użycie dowolnych metod z tworzonych lokalnie obiektów.
- Bawić się zabawkami, które ktoś ci dał – możesz wywoływać metody obiektów przekazanych w parametrach metody.
Zabrania się jednak zabawy „zabawkami zabawek”, czyli w praktyce jeżeli chcemy stworzyć kod taki jak w drugim przypadku to powinniśmy zamiast kilkunastu kolejnych wywołań otrzymać zabawkę – instancję Document i na niej wywołać odpowiednią metodę. Wynik tej metody przekazać niżej jako nasz podarunek dla wywoływanego obiektu.
W pierwszym przypadku należało by zupełnie inaczej rozmieścić metody. Jako, że naszą zabawką jest obiekt klasy Person to nie możemy wykorzystać metod pola name (zabawka zabawki), nie za bardzo idzie też stworzyć nową zabawkę (np. dynamicznie dodać metodę – brak dynamicznego rozszerzania klas, ale już w Groovym będzie działać). Zostaje zatem zmiana właściciela metody tak by zabawki zostały w słusznej piaskownicy.
Listing 3. Wszystkie zabawki w swoich piaskownicach
package pl.koziolekweb.eowp4;
public class App {
public static void main(String[] args) {
Person person = new Person();
person.name = "Ilona";
System.out.println(person.isFemaleName());
}
}
class Person {
String name;
boolean isFemaleName() {
return name.lastIndexOf("a") == name.length() - 1;
}
}
W ten sposób wszystko jest na swoim miejscu. W dodatku logika też trzyma się kupy. Klasa Person potrafi powiedzieć czy jest chłopcem czy dziewczynką.
Wyjątek
W ogólności unikanie wielu kropek w linii poza wymuszeniem lepszego rozłożenia metod i ulepszeniem ogólnego designu kodu poprawia też jego czytelność. Jest to bardzo dobre jeżeli np. korzystamy z Criteria API czy też podobnych wynalazków gdzie „da się” tworzyć konstrukcje typu przekazanie jako parametru metody trzech wywołań jakiś metod. Jest jednak pewien wyjątek od tej zasady. Jest nią wykorzystanie fluent interface.
Hej, ale tam są zabawki zabawek!
No nie do końca. Założeniem tego modelu programowania jest niezwracanie void. Jeżeli jakaś metoda nie zwraca nic to powinna zwracać this. W ten sposób możemy łatwiej tworzyć czytelny kod. Przy czym czytelność będzie zależała w znacznej mierze od konfiguracji IDE 🙂 Świetny przykład pokazał Jacek Olszak we wpisie o FI w EggFramework. W praktyce zabawka zwraca samą siebie (bez klonowania mi tu!).
Podsumowanie
Czwarta z zasad jest z serii tych wrednych. Z jednej strony bardzo prosta do wprowadzenia, ale jednocześnie obnaży nawet najmniejsze nieprawidłowości w projekcie klasy. Zasada ta świetnie sprawdza się jeżeli chcemy utrzymywać porządek w interfejsie klas i nie do końca wiemy gdzie umieścić daną metodę.
Szczególnie pomocna staje się ona w momencie gdy korzystamy z kontenera DI. W takim momencie bardzo łatwo jest określić czy dana operacja powinna być wykonana na danym poziomi, czy też lepszym rozwiązaniem nie będzie przeniesienie jej w głąb i udostępnienie jako pojedynczej operacji.