czwartek, 25 grudnia 2008

Bo najważniejsza jest odpowiedzialność Synu...


...najbardziej trafną metaforą obiektów programistycznych jest organizm biologiczny. Podobnie jak komórki, obiekty "nie wiedzą", co dzieje się wewnątrz innych obiektów, ale komunikują się z nimi realizując bardziej złożone cele. W przeciwieństwie do takiego organizmu program monolityczny przypomina mechanizm zegarka, zawierający nieprzeliczalną (sic!) liczbę trybików. Trybiki nie mają żadnej inteligencji i są bezużyteczne poza mechanizmem, w którym pracują. Taki projekt nie ma szans powodzenia. Budując mechanizmy zegarowe, dochodzisz w końcu do takiej złożoności, że całość przestaje funkcjonować.


Heh czyli powiedzenie "działa jak w zegarku" nie jest komplementem lecz inwektywą:P

Cytat pochodzi z książki Projektowanie obiektowe - Role, odpowiedzialność i współpraca autorstwa Rebeki Wirfs-Brock i Alana McKean, którą to chciałbym w niniejszym poście zarekomendować.

/*
A na marginesie przypomnę, że Rebeka jest autorką ciekawej prezentacji krytykującej podejście do projektu polegające na rozpoczynaniu od zaprojektowania bazy danych - życia nie oszukasz.
*/

Na wstępie uprzedzam iż książka jest mocno abstrakcyjna a miejscami wręcz filozoficzna - raczej nie przypadnie do gustu tym, który idą z duchem czasu - czasu napier....

Książka Rebeki i Alana wprowadza nas w świat Responsibilty-Driven Design. Świat, w którym posłujemy się bytami o ściśle określonych rolach. Nie ma w nim miejsca na radosną twórczość, boskie klasy o wszechmocnej odpowiedzialności i tony spaghetti kodu. RDD z założenia ma na celu uporządkowanie chaosu i przygotowanie się na przyszłą i nieuniknioną ewolucję systemu.

W jaki sposób? O tym właśnie traktuje cała książka. Natomiast w skrócie sposób zasadza się na świadomym i konsekwentnym przypisywaniu tytułowych ról, odpowiedzialności i współpracy.

Czym jest współpraca i odpowiedzialność - myślę, że wszyscy intuicyjnie wiemy. Zasady GRASP są jasne w tych kwestiach:
- współpraca - utrzymujmy jak najmniej powiązań (Low coupling)
- odpowiedzialność - im niej tym lepiej (najlepiej niech klasa ma jedną odpowiedzialność) - w skrócie: dążymy do wysokiej kohezji klasy.


Natomiast tytułowe role to nic innego jak pewne zakresy odpowiedzialności. W RDD nie bawimy się już radośnie definiując na chybił trafił klasy a w nich metody (najlepiej statyczne hehe) typu załatwZprawę() albo zróbDobrze(). Nie, w ten sposób daleko nie zajdziemy - co najwyżej do wersji 1.0. W RDD myślimy nie na poziomie klasek ale na poziomie bytów pewnych typów zajmujących się ściśle pewnymi odpowiedzialnościami.

W RDD wyróżniamy zestaw głównych stereotypów ról, które mogą być grane przez nasze obiekty:
  • magazyn informacji (information holder) - przechowuje i dostarcza informacje
  • łącznik (structurer) - utrzymuje relację pomiędzy obiektami oraz informacje o tych relacjach
  • dostawca usług (service provider) - wykonuje zadania oraz, w ogólności, oferuje usługi obliczeniowe
  • koordynator (coordinator) - obsługuje zdarzenia przez przekazywanie zadań do innych obiektów
  • kontroler (controller) - podejmuje decyzje i bezpośrednio kieruje zadaniami innych obiektów
  • transformator (interfacer) - przekształca informacje i żądania pomiędzy różnymi częściami systemu
Role, odpowiedzialności i współpraca są głównym tematem książki lecz nie jedynym. Właściwie to są osią wokół której pojawiają się konteksty inny problemów projektowych, takich jak: style architektoniczne, elastyczność, analiza obiektowa, style obsługi błędów...

Można oczywiście zadać sobie pytanie: co mnie obchodzi jakiś RDD? To pewnie kolejna ściema dzięki której ktoś miał temat na książkę, kilka prezentacji w fajnych hotelach, temat na blogaska i oczywiście... źródło łatwych dochodów;P

Wg mnie warto jednak zastanowić się nad paroma koncepcjami zawartymi w RDD ponieważ wydają się one być uogólnieniem Domain Driven Design. W DDD mamy building blocks (o których już pisałem) służące do modelowania dziedziny: encje, VO, agregaty, polityki, servisy, repo i fabryki.

RDD unosi problem standardowych artefaktów na nieco wyższy poziom abstrakcji. Wspomniane stereotypy ról pokrywają building block z DDD. Ale stereotypy ról nie ograniczają się jedynie do klocków służących do modelowania dziedziny. Standardowe role pokrywają cały system - nie tylko warstwę biznesową.

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

A co by było gdyby język wspierał role na poziomie składni;)))? Gdyby zamiast public class TaxService{} deklarować public service Tax{} albo z innej beczki: public coordinatror EventManager{}. Wówczas może kompilator mógłby jakoś magicznie sprawdzać czy klasa nie robi tego co do niej nie należy i chronić zbyt cwanego programistę przed stworzeniem Potwora Spaghetti:)

Eeee... chyba wczorajsza dawka strawy z nadmierną ilością ryb i niedostatkiem wieprza, do której mój prasłowiański żołądek nie jest przystosowany, skutkuje dzisiejszym majaczeniem;)
Przecież na początek wystarczyłyby adnotacje w jakimś nowym, pięknym frameworku:)

niedziela, 14 grudnia 2008

UP-DDD in Action: Hermetyczne agregaty



Drogie dzieci - w dzisiejszym odcinku z serii UP-DDD in Action zobaczycie jak nasz bohater Bob Budowniczy rozprawia się z brakiem hermetyzacji i proceduralną ignorancją.



Kontynuując temat enkapsulacji rozpoczęty w poprzednim poście zobaczmy jak wygląda on w kontekście Domain Driven Design.

DDD opiera się na starych i sprawdzonych technikach obiektowych więc proceduralne łańcuszki typu "train wreck" zbesztane w poprzednim poście nie mają prawa pojawić się w kodzie aspirującym do miana DDD.

Analizując specyfikacje building blocks z DDD widać, że Encje, VO i Agregaty posiadające odpowiedzialność biznesową (nie będące standardowym anemicznym modelem) są w zgodzie z paradygmatem Abstrakcji.

Dodatkowe założenie odnośnie Agregatów jest takie, że hermetyzują one swą strukturę. Oznacza to, że nie powinny zdradzać szczegółów swych składowych. Agregat posiada root (główną encję), która publikuje niejako zestaw odpowiedzialności/zdolności w celu ochrony wnętrzności przed światem zewnętrznym. Jeżeli świat zewnętrzny nie wie nic o wnętrznościach agregatu, to wówczas implementacja agregatu może z czasem się zmieniać (aby nadążać za nowymi wymaganiami). Dzięki temu zmiany są lokalne i nie mają wpływu na świat zewnętrzny.

Weźmy na ten przykład arcy interesujący model zamówienia;) Niech zamówienie posiada datę, miejsce dostawy oraz listę zamówionych produktów. Zamówienie będące agregatem nie może na przykład udostępniać gettera do listy produktów ponieważ istnieje możliwość modyfikacji tejże listy "poza świadomością" samego zamówienia.
Zakładając iż tworzymy obiektowy model biznesu i jego reguł to chcielibyśmy aby każda operacja na produktach była wykonywana poprzez samo zamówienie, które jest swego rodzaju mózgiem biznesowym.

Obiekt zamówienia posiadając wiedzę o swych wnętrznościach może na ten przykład domówić dodanie do niego pewnego produktu jeżeli miejsce dostawy zawiera się w pewnej strefie, klient nie jest pełnoletni, albo data realizacji wykracza poza jakąś tam granicę pewnej akcji marketingowej.

W tym akurat przykładzie jakimś rozwiązaniem byłoby upublicznienie iteratora po iście zamówień. Ale załóżmy przypadek bardziej ogólny - nie chcemy zdradzać żadnych szczegółów i kropka.

No dobra, wystarczy tego teoretycznego bełkotu! W praktyce niestety często istnieje konieczność dobrania się do wnętrzności obiektu. Na przykład po to aby wyeksportować zamówienie do PDF lub XML. Że o takim szczególe jak jego wyświetlenie na GUI nie wspomnę;P

Podchodząc do zamówienia jak do struktury danych potrzebowalibyśmy procedur, które odpowiadają za przejrzenie wnętrzności zamówienia i stworzenie odpowiednio PDFa lub XMLa prezentującego dane o zamówieniu i jego wewnętrznej strukturze (GUI to problem osobny - wręcz filozoficzny, więc o nim osobny akapit później).

Na szczęście istnieje wzorzec projektowy Budowniczego. Idea wzorca jest prosta jak nie przymierzając budowa cepa. Zakłada ona istnienie dyrektora budowy i robotnika (budowniczego). Dyrektor wie CO trzeba zrobić a robotnik wie JAK to zrobić.



Na powyższym przykładzie klasa Order jest dyrektorem budowy. Gdy ktoś poprosi dyrektora o wyeksportowanie zamówienie wówczas on wydaje rozkazy delegując pracę do budowniczego. Dyrektor (zamówienie) wie skąd pobrać dane do eksportu - z własnych prywatnych wnętrzności. Natomiast robotnik (budowniczy) jest abstrakcją w najczystszej formie - interfejsem. Konkretny budowniczy wie jak zająć się surowcami przekazywanymi mu przez dyrektora budowy. Na przykład budowniczy PDFów zna doskonale jakiś silnik do generowania dokumentów. Budowniczy XMLi zna format w jakim należy zbudować plik tekstowy. Budowniczy DTO wie jak należy skonstruować obiekt transferowy.

Mamy tutaj doskonałe rozdzielenie odpowiedzialności: hermetycznych struktur biznesowych i wiedzy o ich semantyce od technicznych aspektów formatów danych eksportowych.

Powyższy przykład można rozszerzyć o mały aspekt optymalizacyjny. Bo co jeżeli dany budowniczy nie jest z jakiegoś powodu zainteresowany na przykład listą zamówień? Najprościej byłoby aby jego implementacja metody buildNextProduct() po prostu ignorowała przekazywane produkty. Niestety mamy wówczas niepotrzebną iterację. Zamiast tego możemy rozszerzyć interfejs budowniczego o metodę isProductListNeeded(). Iterujemy po liście jedynie wówczas gdy metoda zwróci wartość true.



Wspomniałem wcześniej o aspekcie GUI. Popularne frameworki zasadzają się na refleksji i wymagają by istniały gettery do składowych obiektu, które chcemy wyświetlić. Heh jednym ze sposobów na wyświetlenie zamówienie w JSF mogłoby być stworzenie takiej implementacji budowniczego, która tworzy jakiś UIComponent który to następnie dodajemy do widoku - niestety jak dla mnie jest to nakładanie gaci przez głowę.

Jakimś rozwiązaniem jest zastosowanie Data Transfer Objects. Czyli nasz agregat biznesowy może eksportować się do DTO, które to DTO jest bindowane z widokiem. DTO ma już gettery i settery więc doskonale pasuje do frameworków.

Rozwiązanie to ma oczywiście plusy dodatnie i ujemne.

Przede wszystkim dzięki przepakowaniu obiektów biznesowych do głupich DTO nie udostępniamy w warstwie UI metod biznesowych (pamiętajmy, że Agregaty je posiadają). Serwisy aplikacyjne posługujące się DTO nadają się wówczas do bycia Web Serwisami - nie chcemy przecież upubliczniać naszego modelu biznesowego oraz jego metod (odpowiedzialności). DTO mogą zawierać jedynie istotne z punktu widzenia usługi dane, prezentować je w spłaszczony (prostszy) sposób oraz stanowić warstwę interfejsu do systemu pod którą możemy swobodnie zmieniać nasz model bez obawy o ciągłe modyfikacje klientów.

W małych projektach zmniejsza to oczywiście w znaczny sposób produktywność (rapid developnent hehehe). Zamiast spokojnie stukać sobie przeglądarkę do bazy musimy nakładać gacie przez głowę i przepakowywać dane bawiąc się samemu ze sobą w kotka i myszkę:/


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

Swoją drogą to dziwne, że frameworki nie wspierają dobrych modeli programowania a skupiają się jedynie na przykryciu technikaliów takich jak na przykład HTTP, transakcje, rozproszenie obiektów, bindowanie danych warstwą abstrakcji...

Nawet frameowrki, które nazywają się dumnie frameworkami aplikacji nie wspierają żadnego modelu aplikacji a hermetyzują jedynie niskopoziomowe aspekty techniczne.

W którymś z następnych odcinków zobaczymy jak wygląda to w http://www.qi4j.org/

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.