poniedziałek, 8 grudnia 2008

Flaczki (Kod obiektowy - zawsze smaczny i zdrowy)



Zacznijmy od oczywistych oczywistości...
Programowanie Obiektowe zasadza się na 4 paradygmatach:
  • Abstrakcja
  • Enkapsulacja
  • Polimorfizm
  • Dziedziczenie
O złym jak Charles Manson dziedziczeniu w ogóle i złudnym dziedziczeniu wielokrotnym już pisałem:
- Telefonistka dziedziczy po telefonie
- Strategia na wielokrotne dziedziczenie
teraz kolej na enkapsulację i abstrakcję.

Czy tego typu kod jest obiektowy?
czlowiek.getOtrzewna().getZoladek().getTresc().add(new Kielbasa(2));
Na pierwszy rzut oka tak. Mamy piękne obiekty złożone we wspaniałą strukturę. Niestety analizując nawet niezbyt dogłębnie definicje 4 paradygmatów wymienionych jako oczywiste oczywistości możemy o tym kodzie powiedzieć, że co najwyżej używa obiektów ale obiektowy nie jest. Ba, można nawet pokusić się o stwierdzenie, że żadnych obiektów nie używa.

Przykładowy kod jest niezgodny z paradygmatem Enkapsulacji ponieważ literalnie wypruwa flaki z biednego człowieka. Obiekt człowiek ujawnia swą wewnętrzną strukturę oraz poniekąd implementację. Taki obiekt jest kompletnie nieodporny na zmiany ponieważ na ten przykład próba uszczegółowienia anatomii w wersji 2.0 i dodania nowych narządów spowoduje katastrofę. Enkapsulacja nie polega na bawieniu się samemu ze sobą w kotka i myszkę: prywatne pola ale za to publiczne gettery - po co się w ogóle tak wydurniać?

Przykładowy kod jest niezgodny z paradygmatem Abstrakcji ponieważ literalnie człowiek nie ma nic do gadania w temacie karmienia go kiełbasą. Co jeżeli dana instancja człowieka jest już syta albo nie lubi podwawelskiej?

Przykładowy kod jest to tak zwany "train wreck" - kod charakterystyczny dla struktur danych w językach proceduralnych na których to strukturach operują zewnętrzne procedury. Owszem może było to dobre jeszcze w szalonych latach '70 ale problem polega na braku hermetyzacji.

W odpowiedzi na bolączki proceduralnego kodu powstała właśnie koncepcja OO. Koncepcja, która sugeruje aby naszego przykładowego człowieka wyposażyć w odpowiedzialność lub możliwość pożywiania się:
czlowek.zjedz(pokarm) - wewnętrzna implementacja człowieka zdecyduje co z tym zrobić. W szczególności instancja człowieka może uznać, że sugerowany pokarm należy natychmiast wypluć
Możemy pójść jeszcze dalej i wyposażyć człowieka w umiejętność brania: czlowiek.wez(pokarm) - instancja człowieka może uznać za stosowne schowanie go do kieszeni, plecaka, lub w nawet poddanie obróbce przed spożyciem.

Ten sam styl rozumowania aplikuje się do obiektów agregowanych w ramach człowieka. Nie wystarczy zadeklarować metodę czlowek.zjedz(pokarm) a w niej radośnie posługiwać się łańcuszkiem
this.getOtrzewna().getZoladek().getTresc().add(pokarm);
Kontynuując obrany tok rozumowania należny metodę czlowek.zjedz(pokarm) zaimplementować mniej więcej tak: this.ukladPokarmowy.przyjmij(pokarm);
i tak dalej...
A taki układ pokarmowy lub pojedynczy narząd może być całkiem za darmo reużyty dla innych ssaków:)

Z punktu widzenia kodu wchodzącego w interakcję z obiektem człowiek nie jest istotne co instancja człowieka zrobi ani jak to zrobi. Dopóki człowiek enkapsuluje to pod stabilnym interfejsem kod jest "otwarty na rozbudowę niczym kwiat lotosu o świcie i zamknięty na modyfikacje niczym kwiat lotosu o zmierzchu" (czy jakoś tak;)

//=====================

Train Wrecks są dosyć popularne lub wręcz lansowane przez standardowe frameworki i architektury. Począwszy od encji hibernate przez encyjne EJB po ich łatwe bindowanie z kontrolkami w warstwie prezentacji. Może jednak coś w tym jest;)

W następnym odcinku zobaczymy co na to Domain Driven Design.

Brak komentarzy: