Ekstremalna obiektowość w praktyce – część 9 – Nie używaj getterów/setterów/własności

Część 0
Część 1
Część 2
Część 3
Część 4
Część 5
Część 6
Część 7
Część 8

Piątek, a zatem słuchamy Listy w Trójce. Przed nami ostatnia z zasad Jeff’a Bay’a

Nie używaj getterów/setterów/własności

Niewątpliwie jest to najbardziej obiektowa z zasad. By zrozumieć o co w niej chodzi porównajmy takie oto dwie klasy

Listing 1. klasa bardziej „obiektowa”

class Chłopiec{
	
	private String imię;
	
	private String nazwisko;
	
	private String wiek;

	public String getImię() {
		return imię;
	}

	public void setImię(String imię) {
		this.imię = imię;
	}

	public String getNazwisko() {
		return nazwisko;
	}

	public void setNazwisko(String nazwisko) {
		this.nazwisko = nazwisko;
	}

	public String getWiek() {
		return wiek;
	}

	public void setWiek(String wiek) {
		this.wiek = wiek;
	}
	
}

Listing 2. klasa mniej „obiektowa”

class Dziewczynka {
	
	public String imię;
	
	public String nazwisko;
	
	public String wiek;

}

Czym różnią się te dwie klasy?

Obiektowość to nie metody

Pomijając różne specyficzne elementy języka związane z współbieżną zmianą stanu obiektów tych klas to te dwa twory niczym się nie różnią. Mogę jednak założyć się, że jeżeli pokażemy je „nagie i bezbronne” to pierwsza z nich zostanie zakwalifikowana jako bardziej obiektowa. Druga będzie mniej obiektowa. Dlaczego? Jakoś tak się utarło, że o obiektowości decydują metody.
W Javie jest to szczególnie potęgowane przez specyfikację Java Beans:

Properties are always accessed via method calls on their owning object. For readable properties
there will be a getter method to read the property value. For writable properties there will be a
setter method to allow the property value to be updated.

Cóż… Ma to pewien cel i jest tu pewien pomysł, ale tylko gdy przyjrzymy się tej specyfikacji jako całości (szczególnie z obsługa zdarzeń i wzorcem obserwatora). Niestety jak to w życiu bywa, ktoś z całego dokumentu wyciągnął jeden fragment i tak oto znaleźliśmy się w życi.

Czym jest obiektowość?

Obiektowość to przede wszystkim enkapsulacja. Inaczej mówiąc obiekty nie powinny ujawniać swojego wnętrza, a jedynie udostępniać pewien interfejs, który będzie odwzorowywał operacje dokonywane przez obiekt. Oczywiście może się tak zdarzyć, że operacja polega na ustawieniu jakiejś wartości obiektu. Jednak są to specyficzne przypadki. W żadnym wypadku nie należy pozwalać na swobodną manipulację właściwościami obiektu.

Co zamiast?

Skoro nie powinniśmy mieć możliwości swobodnej manipulacji jak tworzyć i konfigurować obiekty? Z każdym z elementów zajmiemy się z osobna.

Settery

Generalnie obiekty powinny być niezmienne. Ma to wiele zalet. Najważniejszą jest możliwość swobodnego programowania współbieżnego bez obawy, że wątki wzajemnie pozmieniają sobie dane. W takim wypadku settery zostaną automatycznie wyrugowane przez mechanizmy języka wymuszające ustawienie pól w konstruktorze.
Jeżeli jednak z jakiegoś powodu nie możemy użyć pól finalnych to należy ustawić je z użyciem jakiegoś kontenera DI. Zazwyczaj mamy dostęp do tego typu bibliotek i nie ma problemów z ich wykorzystaniem.
Co jednak gdy z jakiś powodów kontener DI nie jest dostępny? W takim wypadku zostaje nam wykorzystanie wzorca fabryki wzbogaconego o użycie refleksji.

Właściwości

Unikamy sytuacji kiedy zachowanie obiektu zależy od właściwości. To właściwość powinna dostarczać odpowiedniego zachowania. Przykładowo w poprzedniej części mówiłem, że opakowane kolekcje powinny dostarczać metod „kontekstowych”. Podobnie w trzeciej części gdzie mowa była o opakowaniu typów prymitywnych wspominałem o możliwości zamknięcia w typie opakowującym pewnych prostych operacji np. walidacji czy operacji arytmetycznych.

Gettery

Najbardziej kłopotliwy element. Koniec końców zawsze musimy gdzieś pokazać uzyskaną wartość. Najprościej jest przesłonić toString. Nie zawsze się to da zrobić. O ile zadziała to w przypadku prostych obiektów to już obiekty złożone stanową duży problem. Pewnym rozwiązaniem może być użycie odpowiedniego prezentera, czyli wzorca MVP. Prezenter może dostać się do obiektu via refleksja względnie odpowiednio zinterpretować wyniki z metody toString. Wzorzec ten ma w tym przypadku wiele zalet. Jeżeli jednak chcemy zrobić coś małego to można użyć czegoś co roboczo nazywam obiektem „samopezentującym się”. W takim przypadku obiekt posiada metodę, do której można przekazać docelowe miejsce prezentacji np. strumień i tyle. Jest to rozwiązanie o tyle wygodne, że czasami chcemy ograniczyć możliwość prezentacji obiektu. W takim wypadku metoda pełni rolę swoistego „pasa cnoty” ponieważ ogranicza kanały prezentacji.

Podsumowanie

Uff… przebrnąłem. Nie było łatwo. Paweł Lipiński w komentarzu do siódmej części zwrócił uwagę, że są to przede wszystkim pewne heurystyki. W części zerowej wspomniałem, że zasady te są świetne jeżeli chcemy refaktoryzować kod. Są też doskonałym narzędziem do ćwiczenia. Zatem czy stosować zasady Jeff’a Bay’a? Moim zdaniem warto dążyć do kodu napisanego zgodnie z tymi zasadami. Ponieważ kod taki promuje przemyślane konstrukcje, pozwala na odnalezienie potencjalnie niebezpiecznych fragmentów i karkołomnych konstrukcji w architekturze oraz wymusza pisanie testów. Ja napisałem projekt zgodny z tymi zasadami mający około 1000 linii. Teraz czas byś usiadł i napisał swój projekt.

14 myśli na temat “Ekstremalna obiektowość w praktyce – część 9 – Nie używaj getterów/setterów/własności

  1. A moze moglbys wystawc ten projekt 1k linii gdzies publicznie? Czy to wewnetrzna wybitnie robota w pracy i ni cholery sie da?

  2. Wybitnie wewnętrzna robota – integracja systemów płatności internetowych. Mogę jednak machnąć „coś w ten deseń”.

  3. Tylko czy było by to lepsze. Nie zawsze chcemy by nam grzebano po obiektach. W praktyce dostęp do pól jest potrzebny tylko na poziomie prezentacji danych. Wszędzie indziej powinniśmy dogadywać się z obiektami za pomocą interfejsów bez wnikania w ich wewnętrzną strukturę.

  4. Za trolla zostanę uznany bo znów się nie zgadzam z Tobą 😉

    O ile zasada słuszna, o tyle nie pasuje mi za nic interpretacja przy getterach.
    Proponowanie refleksji zamiast getta? Proszę Cię, trochę to naciągane, jednak wolę gettery niż zaklinanie rzeczywistości by dopasować się do nawet najlepszej zasady.

    Bliżej mi do delegowania kolejnych odpowiedzialności, klasie która miała by gettera otrzymać. Gorzej, ze może to nas zapędzić w kozi róg. Mój wczorajszy (dzisiejszy?) wpis odnosi się do serii screencastow Jamesa Shora, gdzie delegował on wyświetlanie do VO poprzez specjalne interfacy. Skończyło się to dość tragicznie i przypuszczam, ze gdyby to była większa aplikacja i jego VO musiało dostać więcej odpowiedzialności to skończyło by się to albo kilometrowa klasa wiedząca jak się renderować, zapisać, zserializować do XMLa, etc.
    Niemniej tego podejścia nie potępię więcej bo działa. Chętnie bym to zobaczył w większym projekcie.

    Nie czuję też by toString był dobrym miejscem do produkowania wartości dla użytkownika. Osobiście preferuję jednak używać go jako metody technicznej, np do loggerów. toString ogranicza nas też do reprezentacji obiektu jako String, a to nie zawsze jest pożądane.
    No ale jeśli już wykorzystujemy toString to dołóż do niego javadoca. Wtedy taki ja będzie wiedział by nie popsuć Ci metody biznesowej 😉

    A idealnym rozwiązaniem jest dla mnie, ciągle, pewnie niestety, okazyjny get, tam gdzie jego spowoduje zmniejszenie kombinacji i pozwoli na uproszczenie kodu.
    Niby młody jestem a czuję się jak programista starej daty 🙁 😉

    Pozdrawiam,
    Michal

  5. Ej, na refleksji działają JSyFy (konstrukcje bean.pole). Dobrym rozwiązaniem jest zastosowanie MVP z prezenterem, który potrafi komunikować się z obiektami po specjalnym intrefejsie, ale ten interfejs jest niewielki. A tak generalnie to prezentowanie danych w każdej formie innej od bezpośredniej serializacji do ObjectStream ssie.

  6. W wielu przypadkach zagadnienie sprowadza się do dodawania wielu operacji do klasy. Pod pewnym względem elegancko byłoby mieć wszystkie operacje dotyczące klasy Foo jako metody klasy Foo (dość oczywiste). Ale w efekcie dostajemy stado metod typu: toXML, updateDatabase, validateContents, computePrice, itd. Koszmar.
    Rozwiązaniem może być wzorzec Visitor. Pozwala na dodawanie operacji do klasy bez modyfikacji tej klasy.
    Pomysł z refleksją to jakaś jazda po bandzie.

  7. Koziołek, co masz na myśli mówiąc, że JSF bazuje na refleksji? Co prawda ostatni raz miałem z nim do czynienia w wersji 1.2, ale z tego co pamiętam to do bindowania pól na formularzu wymagane były obiekty Java Beans. Nie mogę znaleźć jakiś rzetelnych informacji o tym co nowego pod tym względem w wersji 2.x.

    Moim problemem, jeśli chodzi o gettery i settery, jest to, że mam sobie obiekt, który ma powiedzmy ok 60 pól i muszę udostępnić edycję tychże pól dla użytkownika i perfidnie nie chce mi się tworzyć żądnych sztucznych obiektów transportujących dane z formularza do właściwego obiektu, bo trzeba to potem utrzymywać, dlatego zostaje przy getterach i setterach – choć nie jestem ich wielkim fanem i szczerze to chciałbym się ich pozbyć…

    A może jestem ślepy i są jakieś rozwiązania, które załatwiają mój problem??

  8. @Andrzej, przepraszam, że z takim opóźnieniem, ale jestem w miejscu gdzie kończy się internet 🙂

    1. Specyfikacja JavaBeans mówi nam o dwóch rzeczach. Pierwszą z nich, która jest regularnie olewana, to model zdarzeniowy przy tworzeniu klas. Opis dotyczy m.n. obserwatorów listenerów zmiany stanu obiektów itp. Drugą jest „uspecyfikowany” dokument Code Convention, z którego pobrano np. zasady nazewnictwa getterów i setterów. I właśnie na tych zasadach i refleksji opierają się wszystkie framworki wykorzystujące szablon bean.field. Wynika to z prostego powodu. By wywołać metodę w Javie trzeba po pierwsze znać jej nazwę, a po drugie musi być ona dostępna w trakcie kompilacji. Nie da się zatem napisać jak w Scali

    a < - metoda()

    (poprawcie mnie jak się pomyliłem) co oznacza "obiekt klasy mającej na pokładzie metodę - metoda". JSF mówi nam tylko tyle "daj mi obiekt zgodny z JavaBeans, a ja już będę czarował". Te czary to właśnie użycie refleksji nazwa z szablonu zapisana z wielkiej litery i poprzedzona get == nazwa metody > wywołać.
    2. Na początek przyjrzałbym się innym miejscom w aplikacji, szczególnie formularzom, i poszukał powtarzającego się kodu. Przy tak dużym formularzu na pewno da się wyszukać powtarzający się zestaw pól. Te można zamknąć do mniejszej klasy i zastąpić nią miejsca gdzie następuje wywołanie.
    3. Jeżeli nie chce ci się pisać getterów i setterów i nie wykorzystujesz części specyfikacji JavaBeans poświęconej zdarzeniom w trakcie zmiany wartości pola to możesz użyć takiego ustrojstwa co się nazywa Lombok (https://koziolekweb.pl/2011/03/11/projekt-lombok-czyli-mniej-kodu/). Jest to rozszerzenie kompilatora, które w oparciu o adnotacje doda gettery i settery do klasy. Jednak to już jest ostateczność :)

  9. @Koziołek

    @1. Ok, chciałem się tylko upewnić, czy np. nie zaszła jakaś zmiana w JSF i czy przypadkiem refleksja nie bazuje już bezpośrednio na polach a nie setterach, jest po staremu czyli settery zostają. Choć takie wstrzykiwanie bezpośrednio pod pole to też byłoby łamanie enkapsulacji, tylko w bardziej wyrafinowany sposób.

    @2. Nie bardzo rozumiem, nawet jeśli „podobne” zestawy pól mam już opakowane jakimś komponentem, to nadal pozostaje ten sam problem, albo używamy getterów/setterów na widokach, albo tworzymy DTOsy. A czy to będzie 1 DTO z przypisywaniem 60 pól, czy 10 DTOsów po 6 pól, to sprowadza się do tego samego – trzeba dane z formularza przenieść do obiektu domenowego i vice versa.

    @3. Pachnie hardcorem:)

  10. Nie musisz używać getterów i setterów. Wystarczy, że obiekt opakowujący typ prosty będzie umiał zaprezentować się np:

    public void displayMeIn(StringPresenter presenter){
    presenter.display(this.myPrivateStringValue);
    }

    Do czegoś takiego przydały by się oczywiście miksiny, ale od czego mamy scalę? Względnie statyczne importowanie plus domyślne implementacje jako klasy statyczne w interfejsach.

    Lombok jest hardkorem. Używam czasami jak mam mały projekt i nie chce mi się kodować.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax