Monday, 16 April 2012

Getters and setters - the good parts


Każdy ich używa... ale czemu?

Co nam dają settery i gettery?

- Ukrywają implemenetację!
- Czyżby?
- No tak...
- Na pewno?
- No chyba...

Czym się różni:
public String name;
od:
private String name;

public String getName() { return name; }
public void setName(String name) { this.name = name; }

bo przecież nie ukrywaniem* czegokolwiek...

Owe ukrywanie, czyli enkapsulacja, to jeden z najważniejszych aspektów OOP i zarazem jeden z częstszych motywów podczas rozmów kwalifikacyjnych, więc warto zgłębić ten temat nieco... głębiej. Tym bardziej, że studiach się o tym chyba (a może nie słuchałem...) nie mówi, natomiast w pracy na takie "pierdoły" nie ma czasu. O samej enkapsulacji będzie innym razem, także w kontekście wzorców.

Wracając do setterów i getterów... istnieją ortodoksyjni wojownicy obiektowości głoszący "why getter and setter methods are evil" - http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html - wygląda na fajny artykuł, jednak nie czytałem go, bo spodziewam się co będzie tam napisane. Tak, podejście rodem z JavaBeans ma swoje wady, wśród których prym wiedzie gigantyczny znak zapytania przy "ukrywaniu".

Co jest więc takiego dobrego w setterach i getterach, że były, są i będą używane?

Po pierwsze, wracając do powyższego porównania publicznego pola name typu String z polem prywatnym i akcesorami... Te 2 kawałki kodu nie różniłby się niczym, gdybyśmy nie mieli opcji zmiany tego, co zaproponował nam kompilator podczas wspaniałomyślnego wygenerowania gettera i settera. Settery dają możliwość walidacji danych! Chcesz zamieniać polskie znaki w Stringu na ich "okrojone" odpowiedniki? Nie ma sprawy - zmieniasz jednego settera - i już, wszystko działa. Tak, jednego. Mając do czynienia z polami publicznymi stykamy się z czymś bardzo hardcorowym - wprowadzając zmiany w logice musimy zrobić to w każdym miejscu, gdzie to pole jest ustawiane, to byłoby nie tylko sprzeczne z podstawowymi zasadami programowania, ale po prostu skrajnie głupie.

Wracając jednak do "walidacji" - w każdym przypadku, gdy w setterze dzieje się coś więcej niż standardowe przypisanie nowej wartości (np. walidacja, jakieś efekty uboczne, rzucanie wyjątkiem) trzeba opisać to w Javadocu - wtedy i tylko wtedy. Dokumentowanie tych o domyślnym działaniu to strata czasu.

Po drugie, o dziwo czasem jednak "ukrywają" ;) czyniąc kod odporniejszym na zmiany.

przed:
private boolean alive = true;

public boolean isAlive() { return alive; }
public void setAlive(boolean alive) { this.alive = alive; }
po:
private int hp; // zmiana implementacji!

public boolean isAlive() { return hp > 0; } // stare sygnatury
public void setAlive(boolean alive) { this.hp = alive ? 100 : 0; }
Jest to skrajny przypadek, ale i takie się zdarzają. Zmieniła się implementacja, interfejs pozostał niezmieniony, klasa nadal spełnia swój kontrakt, brawo dla niej, brawo dla nas ;)

//@TODO: jakiś oczywisty kontr przykład - np. zamiana typu z int na long, aby pokazać, że powyższe to raczej wypadek przy pracy niż reguła :)

Po trzecie - polimorfizm. Warto o tym pamiętać, że używanie getterów, setterów i javadoców czyni możliwym to, co byłoby niemożliwe przy zwyczajnym dziedziczeniu publicznego pola. Pola nie są polimorficzne.

Po czwarte - debugging. W środku metody możemy w razie uzasadnionej potrzeby dorzucić breakpointa albo logowanie. Znowu - robimy to tylko jeden raz a efekt staje się globalny.



Ok, to tyle, następnym razem będzie w drugą stronę (czyli co zamiast getterów i setterów), ale na zakończenie jeszcze mała przypowieść "why getter and setter methods are GOOD":
Because 2 weeks (months, years) from now when you realize that your setter needs to do more than just set the value, you'll also realize that the property has been used directly in 238 other classes :-)

No comments:

Post a Comment

Post a Comment