Pokazywanie postów oznaczonych etykietą wzorce projektowe. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą wzorce projektowe. Pokaż wszystkie posty

niedziela, 12 stycznia 2014

Artykuł: Building Blocks dla Twojej lewej półkuli

Redaktor naukowy Iain Mcgilchrist w swej książce The Master and His Emissary (będącej podsumowaniem ostatnich 10 lat badań nad mózgiem) posłużył się wymowną metaforą lewej półkuli mózgu jako "salonu luster" - jeżeli wpadnie do niego promień światła, to będzie odbijał się w salonie po ustalonej przez ustawienie luster drodze, zawsze takiej samej.


Lewa półkula myśli indukcyjnie (nota bene: znana ze szkoły indukcja matematyczna jest rodzajem dedukcji:), posługując się "foremkami" na myśli. Tworzy ona narrację rzeczywistości używając tylko takich foremek jakie "zna".

Dlaczego piszę o tym w kontekście programowania?

Bo jeżeli jedyne foremki jakie zna nasza lewa półkula to serwis i anemiczna encja, to jest ona w stanie zamodelować cały świat serwisem i encją (rekordem i procedurą jeżeli wciąż programujesz w Turbo Pascalu lub strukturą i funkcją jeżeli w C).

Da się - wiem, bo tak robiłem:)

Jeżeli jedyna foremka jaką zna nasza półkula to Hashmapa, to wszystko będzie Hashmapą-hashmap:P

Lewa półkula jest również specjalistą w stworzeniu paranoi, że jest to najlepszy z możliwych modeli - bo mój (hmm ciekawe co neuro-naukowcy powiedzieliby na umiejscowienie ego w mózgu...).

Do tej pory wiele pisałem o Building Blocks z wachlarza technik DDD. Tym razem zapraszam do lektury artykułu, który jest poświęcony bardziej ogólnym BB, które można wyróżniać w każdym projekcie, nie stosując nawet DDD: "Building Blocks dla Twojej lewej półkuli: połączenia podejścia obiektowego, proceduralnego, funkcyjnego w codziennej pracy z kodem". Artykuł ukazał się w najnowszym wydaniu Programistamag.

Generalnie chodzi o to aby poszerzyć wachlarz "foremek" jakie stosuje nasza lew półkula do modelowania problemów. Dzięki temu modele staną się bardziej realistycznie (bez sztucznych ograniczeń) i giętkie.

piątek, 7 czerwca 2013

Open/closed principle - zastosowanie na poziomie architektury aplikacji oraz metafory wizualne

"Kod powinien być otwarty na rozbudowę jak kwiat lotosu o świcie i zamknięty na zmiany jak kwiat lotosu o zmierzchu" - jako początkujący projektant natknąłem się kiedyś w jednej z książek na takie "Buddyjskie" wyjaśnienie Open/Closed Principle - jednej z zasad SOLID.

Jednak jak w praktyce zastosować tą zasadę? Czy aplikuje się ona jedynie na poziomie Object Oriented Design czy również na poziomie architektury aplikacyjnej?

W najnowszym artykule "Zarządzenie złożonością przez trójpodział logiki – Open/closed principle w praktyce" opublikowanym na łamach programistamag.pl przedstawiłem swoje przemyślenia na temat OCP, w których integruję:

  • ODD, 
  • podstawy podejścia funkcyjnego, 
  • Building Blocks wchodzące w skład Domain Driven Design,
  • architekturę na poziomie aplikacyjnym.


W artykule chciałbym zaproponować Wam swego rodzaju "framework mentalny", który pozwala zmierzyć się ze złożonymi problemami dzieląc logikę na 3 kategorie:

  • stabilną - której kod relatywnie rzadko podlega zmianom
  • domknięcia logiki - które nie polegają zmianom a rozbudowie
  • wybór domknięć - zmiany enkapsulowane w fabrykach (zgodnie z regułą Uncle Boba: "instrukcje switch są dozwolone jedynie w czeluściach fabryk":)

Zatem jest to meta-model, który pozwala na tworzenie modeli problemów zgodnych z OCP. W artykule znajdziecie również propozycję 3 rodzajów wizualizacji graficznej OCP: jedno, dwu i trójwymiarową - w zależności od preferencji kognitywnych...

Artykuł do pobrania (jak zwykle całkowicie za darmo) tutaj: http://bottega.com.pl/artykuly-i-prezentacje

sobota, 17 marca 2012

Prezentacje na 33rd Degree

W najbliższy wtorek 20. marca będę miał przyjemność przedstawić dwie prezentacje podczas konferencji 33rd Degree.

Niezdecydowanych co do wyboru ścieżki uczestników zapraszam do zapoznania się z prezentacjami. Co prawda są to jedynie "ścieżki myśli" (raczej dla mnie, prowadzące wątek), które bez narracji zawierają max. 20% treści, ale mam nadzieję, że pozwolą Wam zorientować się czy tematyka i jej zakres może być interesująca lub nie.


Pierwsza prezentacja będzie poświęcona zagadnieniom "miękkim". Tradycyjnie wplatam "miękkie" wątki w każdą swą prezentację, jednak tym razem będzie to 100% treści (i zero kodu:) Jednak doszyć szybko doprowadzimy do "ścięcia" się białka i wyszukania "twardych" konkretów - co symbolizuje gubernator na pierwszym "slajdzie":)

abstrakt: Ścisły przewodnik po aspektach miękkich dla ekspertów IT





Druga prezentacja jest dla odmiany techniczna w każdym calu - konkretne problemy i konkretne techniki inżynieryjne ich rozwiązania.

abstrakt: Przewodnik po strukturyzacji systemu. 10,5 klasycznych technik programistycznych leżących u podstaw nowoczesnej inżynierii oprogramowania





//===============================
Materiały są w wersji Alpha, błędy nie zostały jeszcze poddane korekcie:)

środa, 1 czerwca 2011

Sposoby enkapsulowania złożonej logiki biznesowej

W ostatnim poście poruszyłem pewne podstawowe zagadnienia z zakresu projektowania obiektowego, ale jak słusznie zauważyli komentatorzy przykład jest na tyle trywialny, że narzucające się proste rozwiązania są w zupełności wystarczające.

Podejdziemy do problemu raz jeszcze, tym razem w bardziej realistycznym kontekście aplikacji biznesowej.

Mamy zatem ten nieszczęsny OrderItem - nieszczęsny, ponieważ przykład pochodzi z książki, którą ostatnio recenzowałem. Klasyczny model zamówienia: Order-OrderItem-Product


W książkowym przykładzie OrderItem jest obiektem domenowym (rich model) i posiada oprócz stanu również zachowanie - odpowiedzialności biznesowe. Autor książki nawiązuje do Domain Driven Design, dlatego będę się posługiwał Building Blockami modelowania DDD.

Nasz OrderItem zawiera metodę getShippingCost(), która nie jest getterem a metodą biznesową - oblicza koszt dostawy danej pozycji na zamówieniu.

Sugerowana w książce rozbudowa modelu to dodanie nowych klas dziedziczących po OrderItem, które zmieniają sposób liczenia kosztu. Problemy z tym podejściem opisałem w poprzednim poście, w skrócie: w takim obiekcie biznesowym mamy wiele odpowiedzialności, więc prowadzanie dziedziczenia aby zmienić jedną z nich szybko doprowadzi do eksplozji kombinatorycznej bytów.

<dygresja>
Zacznijmy od tego, czy w ogólne taka metoda powinna należeć do tej klasy? W rzeczywistym systemie obliczenie kosztów dostawy zależy zapewne od wielu czynników - nie tylko od zamawianego produktu, ale również od tego kim jest klient, od miejsca dostawy, od reszty zamówienia itd.
Być może cała wiedza znajduje się w klasie OrderItem... Być może wyżej - w Order... Być może nie... wówczas należy wynieść cały problem do osobnego SerwisuBiznesowego (BuildingBlock modelowania DDD).

Nawet jeżeli dziś cała wiedza znajduje się w OrderItem czy nawet w Order, ale w przyszłości zmienią się wymagania i będziemy musieli implementować dziwne akrobacje aby w OrderItem/Order zdobyć potrzebne do obliczeń obiekty.

Generalnie serwis rokuje na lepszy design modelu (serwis biznesowy jest częścią modelu w DDD). W DDD nie chodzi o to aby całą logikę rozsmarować po encjach/agregatach/vo. Nic podobnego. Prosta reguła "kciuka" dla początkujących może być taka: encje/agregaty/vo posiadają metody używane wielokrotnie. Specyficzne metody używane jeden raz znajdują się w specyficznych serwisach biznesowych

Jednak te rozważania nie mają wpływu na dalszą część posta. Abstrahując od tego, którym miejscu umieścimy odpowiedzialność obliczeń wciąż możemy stosować techniki opisane poniżej.

W dalszej części, dla uproszczenia, zakładamy, że większość potrzebnej wiedzy znajduje się w OrderItem.
</dygresja>


W realnej aplikacji obliczenie kosztu dostawy:
- zależy od wielu czynników (rodzaj produktu, klient i jego rabaty, miejsce docelowe, składowe zamówienia, historia zamówień itd)
- jest złożone (załóżmy kilkanaście-kilkaset linijek kodu)

Ta więc rozważania z poprzedniego posta (i komentarzy) gdzie proponowaliśmy switche albo Mapy strtegii się nie aplikują.

Przy standardowym podejściu kod będzie wyglądał mniej więcej tak:

class OrderItem{
  Money getShippingCost(){
    if if if if if
      //17 liniek obliczen
    if if if if
      //50 liniek obliczen z ifami
  }
}
... i wcale nie spodziewajmy się "eleganckiego" switcha, który załatwia sprawę. Mamy tutaj do czynienia z potworkiem rzędu setek linijek, brak ustalenia jednego poziomu abstrakcji, nieustanne skoki mentalne po różnych poziomach oraz potencjał na to, że biznes będzie się komplikował i z czasem wyhodujemy ośmiotysięcznika.

Generalnie przy standardowym podejściu kodzik będzie przeplatał w sobie 3 rodzaje odpowiedzialności:
1. zdecydowanie który scenariusz/algorytm obliczania kosztu zastosować
2. sam algorytm
3. ew. pozyskanie dostępu do danych potrzebnych do obliczeń

Czyli w jednym miejscu mamy 3 rodzaje zależności, o których pisze Misko Hevery.
- colaboration
- construction
- call


Aby uniknąć tego poziomu smutnej złożoności, proponowane w poprzednim poście rozwiązanie (czwarte) sugeruje aby:
1. odseparować samą logikę obliczenia kosztu od reszty kodu.
Wprowadzamy interfejs Strategii, np

interface ShippingCostCalculator{
  Money calculateCost(...);
}

2. kawałki kodu stanowiące każdy z algorytmów pakujemy do osobnej klasy implementującej ten interfejs, np ForeginShippingCostCalculator, LargeItemShippingCostCalculator

2.a co jeżeli wysyłamy duży przedmiot za granicę? tworzymy trzecią klasę ForeginLargeItemShippingCostCalculator? Nie! Z pomocą przychodzi Decorator Design Pattern.

ShippingCostCalculator calculator = new ForeginShippingCostCalculator(new LargrItemShippingCostCalculator());
}

3. kawałki logiki wybierającej, którą konkretną politykę stworzyć chowamy w Fabryce.
I nie jest jeden switch ale kłębowisko ifów

Możemy fabrykować cały agregat:
//fabrykowanie całego agregatu

  class OrderItemFactory{
  OrderItem createOrderItem(...){
    ShippingCostCalculator calculator = //100 ifów;
    return new OrderItem(calculator);
  }
}

albo jedynie strategię:

//fabrykowanie jedynie strategii

  class ShippingCostCalculatorFactory{
  ShippingCostCalculator createShippingCostCalculator(...)
    ShippingCostCalculator calculator = //100 ifów;
    return calculator;
  }
}

Wybór podejścia - co fabrykować: agregat czy strategię zależy do tego w którym momencie mamy wiedzą potrzebną do wyboru kalkulatora. Przed czy dopiero po powołaniu do życia OrderItema.

Generalnie lepiej jest posługiwać się Agregatem i ukrywać szczegóły typu istnienie strategii, ale nie zawsze jest to możliwe.

Tutaj jako agregat traktujemy OrderItem a nie Order, bo tak to wygląda w rzeczywistych systemach, np. klasy ERP - widziałem.

3a. co jeżeli aby wybrać konkretny kalkulator potrzeba znać np aktualnie zalogowanego użytkownika? Lub ogólnie: jakiś kontekst, którego brakuje w OrderItem?
Taka Fabryka może być komponentem zarządzanym (EJB, Speing Bean, itd) i możemy do niej wstrzykiwać potrzebne informacje.

class ShippingCostCalculatorFactory{
  @Inject
  LoggedUser loggedUser;  //logged user to obiekt apliakcyjny (niebiznesowy), trzymany w sesji

  ShippingCostCalculator createShippingCostCalculator(...)
    //if (loggedUser.role == ADMIN)
    ShippingCostCalculator calculator = //100 ifów ;
    return calculator;
  }
}

3b. co jeżeli któraś konkretna strategia obliczania kosztu potrzebuje informacji o zewnętrznym kontekście (np zalogowanym userze)? Chodzi o przypadek gdy metoda z interfejsu strategii nie ma w parametrze tego co potrzebuje implementacja. Fabryka może przekazać kontekst do konkretnej strategii przez jej konstruktor.

class ShippingCostCalculatorFactory{
  @Inject
  LoggedUser loggedUser;  //logged user to obiekt apliakcyjny (niebiznesowy), trzymany w sesji

  ShippingCostCalculator createShippingCostCalculator(...)  
    ShippingCostCalculator calculator = //100 ifów ;
    calculator = new XyzCalculator(loggedUser.getUserId());
  
    return calculator;
  }
}


Podsumowując:
Rozdzielamy silny coupling (współpracy, tworzenia, wywołania) do osobnych "pudełek".
Strategie: tylko obliczenia;
Fabryki: ecydowanie o typie strategii oraz ewentualnie składanie obiektów (przekazywanie szerszego kontekstu do strategii lub dekorowanie strategii)

Dzięki rozdzieleniu możemy niezależnie testować jednostkowo Agregaty i Strategie (Polityki w nomenklaturze DDD). A to za prawą fabryk, w których mieszkają operatory new. Agregaty są od nich wolne, więc mamy możliwość mockowania/stubowania strategii na czas testów.



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

Tak jak pisałem w poprzednim poście: wprowadzenie Strategii zaczyna się nam opłacać dopiero przy złożonych modelach biznesowych. Pokazałem tutaj dodatkowo złożenie Strategii i Dekoratora wraz z idiomem Fabryki oraz wystrzykiwaniem zależności. Dopiero w synergii technik tkwi prawdziwa siła.

Ważne jest to aby w ogóle modelować złożoność. Jeżeli nie modelujemy złożonego problemu a jedynie "zamiatamy go pod dywan" w postaci ukrytych gdzieś głęboko ifów to model szybko upada na twarz;)

Podstawowa zasada Domain Driven Desigm mówi: make explicit what is implict. Czyli wydobywajmy ważne koncepcje biznesowe na powierzchnię. Nie ukrywajmy ich w ifach w linijkacj od 5000 do 6000.

A czy jest coś ważniejszego w modelu biznesowym niż różne sposoby liczenia pieniędzy?;)

Na koniec jedna uwaga: zawsze gdy pokazuję na warsztatach tego typu przykłady część uczestników jest wręcz urzeczona "pudełkowaniem" złożonej logiki w małe, eleganckie pudełeczka. Inni z kolei preferują jedno pudło (np procedurę) ponieważ łatwiej im ogarnąć jedno pudło niż wiele małych, nawet kosztem tego, że w tym jednym pudle jest, powiedzmy delikatnie - bałaganik.

Wszyscy różnimy się mocno pod względem oceniania tego co jest proste. Więcej na ten temat: O programiście-pisarzu i programiście-konstruktorze.

Koszt Dostawy

Odnośnie poprzedniego posta - Jacek L. zadał pytanie o przykład podejścia do liczenia kosztu dostawy.

Mamy kilka możliwości...

Załóżmy, że ma my klasę:

class OrderItem{
  //pola
  metodaBiznesowa1(){..}
  metodaBiznesowa2(){..}
  metodaBiznesowa3(){..}
}


Co jeżeli mamy różne rodzaje/odmiany itemów, gdzie zachowanie tych metod różni się - innymi słowy algorytmy są różne?

1. Rozwiązanie na switchach:

metodaBiznesowa1(){
  switch(jakisParametr){
    case x: return ...
    case y: return ...
  }
}

Konsekwencje: wszelkie zmiany wymuszają "grzebanie" w corowym kodzie biznesowym, czyli jego "brudzenie" czyli ponowne testowanie (nie jest to problem jeżeli mamy pokrycie testami automatycznymi:)

2. Rozwiązanie struktura i algorytmy danych
Generalnie podobne do poprzedniego z tym, że z wyniesieniem kodu biznesowego na zewnątrz - podane dla uzupełnienia wachlarza możliwości:

class OrderItem{
//gettery i settery
}

class BusinessService1{
  doSth(OrderItem oi){
    switch(oi.getSth())
    //...
  }
}

3. SŁABE (ale to zależy od szerszego kontekstu) rozwiązanie z dziedziczeniem:

Tworzymy specjalne klaski dziedziczące po OrderItem, w których nadpisujemy odpowiednie metody biznesowe.

Konsekwencja: mnożenie bytów.

Co jeżeli:
- OrderItem1 nadpisze metodę metodaBiznesowa1
- OrderItem2 nadpisze metodę metodaBiznesowa2
a OrderItemBum chce mieć obie metody biznesowe 1 i 2 takie jak klasy wymienione poprzednio?
Eksplozja kombinatoryczna!

Dziedziczenie jest dobre, ale wówczas gdy klasa ma ściśle określoną odpowiedzialność (jeden powód do zmiany). Wówczas nie ma możliwości zajścia eksplozji kombinatorycznej oraz mamy zwykle pewność zachowania Liskov Substitution Principle.

Co jeżeli klasy dziedziczące są wymagane ponieważ dodają nowe atrybuty?
Wówczas rozwiązanie z dziedziczeniem zaczyna się bronić. Jednak wciąż może dojść do eksplozji klas.
Wówczas lepiej łączyć rozwiązanie z dziedziczeniem z rozwiązaniem 4.

Innym podejściem jest unikanie dziedziczenia i zastosowanie Archetypu Biznesowego Product.

4. Wprowadzenie wzorca strategii.

class OrderItem{
  metodaBiznesowa1(){
    return strategiaBiznesowa1.go(...);
  }
  metodaBiznesowa2(){
    return strategiaBiznesowa2.run(...);
  }
  metodaBiznesowa3(){..}
}

StrategiaBiznesowa1,2 są interfejsami. Konkretne implementacje zajmują się szczegółami.
Unikamy eksplozji kombinatorycznej z podejścia trzeciego dlatego, że w tym przypadku ilość klas to suma możliwych strategi dla każdej metody biznesowej a nie ich iloczyn.

Ale zostawmy autystyczne miary ilościowe... Ten design się po prostu "czuje". Tak jak w naiwnym szkolnym przykładzie: samochód zawiera skrzynię biegów - pasującą do interfejsu (śruby, tryby). Samochód w żadnym wypadku nie dziedziczy po skrzyni biegów:)
//Chociaż kiedyś na rekrutacji ktoś na pytanie o nieksiążkowy przykład dziedziczenia odpowiedział mi: "telefonistka dziedziczy po telefonie".


Skąd OrderItem posiada strategieBiznesowe?

a) OrderItem pobiera z fabrykStrategii - niedobre ze względu na testability

b) OrderItem ma je "wstrzyknięte" z zewnątrz przez FabrykiItemów, które go tworzą lub przez RepozytiumItemów, które pobierają go z bazy.

ew. Fabryki/Repozytoria tworzą nadrzędny agregat (np Order), który zawiera w sobie itemy, ale to zależy od domeny - mechanizm jest ten sam.

To jaką staregięBiznesową "wstrzyknąć" jest decyzją biznesową, tak więc Fabryki/Repozytoria zawierają taką logikę odciążając coupling tworzenia od coplingu używania.
Więcej o rodzajach couplingu dowiemy się od ekspertów Google.

Warto zauważyć, że sama Strategia semantycznie jest niczym innym jak Funkcją (w kontekście programowania funkcyjnego) przekazaną na prawach First Class Citizen. Jedynie syntaktycznie mamy niezręczność wynikającą ze składni Javy;)

Wprowadzenie Wzorca Strategii zaczyna się nam opłacać pod warunkiem, że:
- spodziewamy się pojawiania się kolejnych strategii w przyszłości
- chcemy testować jednostkowo niezależnie logikę OrderItema od logiki StrategiiBiznesowych


Pośrednim rozwiązaniem prowadzącym w kierunku wzorca - dającym potencjał na łatwy refaktoring w przyszłości jest:
1. wyciągnięcie interfejsu
2. dostarczenie jedynej implementacji jako serwisu ze słczami w stylu rozwiązania drugiego.

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

O opłacalności wprowadzenia Strategi możemy mówić również na poziomie modelowania.

Np w DDD nie mówi się Strategia lecz Polityka. Polityki są częścią modelu - obok encji i kilku innych "klocków".
Skutkuje to tym, że zaczynamy pokazywać wariacje istotnych zachowań biznesowych w modelu (diagramach, itd) a co za tym idzie zaczynamy o nich mówić (wchodzą do Ubiquotus Language)
Co najważniejsze: zaczynamy zauważać te arcy-ważne z biznesowego punktu widzenia komponenty - już nie siedzą w słiczach w linijce numer 5000 w serwisie-ośmiotysięczniku;P

czwartek, 19 maja 2011

Stosowalność Wzorców Projektowych

Temat Wzorców Projektowych budzi wiele emocji. Niektórzy są ich zagorzałymi fanami, inni podchodzą z pogardą twierdząc, że w rzeczywistym świecie są bezużyteczne.

Wzorce Projektowe są jedynie narzędziem, a emocjonalne podchodzenie do "młotka" jest po prostu nieracjonalne. Dlatego zaproponuję nieco inne podejście - kontekstowe.

Swoją drogą ciekawe, że podczas rozmów coraz częściej słyszę z ust wytrawnych programistów wyraz irytacji nad bezkontekstowym podejściem naszej branży do wszelkich technik, praktyk, metodyk, produktów, rozwiązań. Mówi się, że jesteśmy sterowani modą bardziej niż Mediolan lub Paryż;)
Odczuwa się silne zmęczenie marketingiem.

W swojej karierze programistycznej miałem okazję pracować nad systemami różnej klasy. Przez klasę rozumiem klasę problemów jakie stanowiły główne wyzwania. Ale to praca trenera i konsultanta dała mi dostęp do wglądu w spektrum klas problemów, w ilości jaką standardowo kolekcjonuje się w okolicach emerytury.

Piszę o tym aby przejść do kontekstu stosowalności Wzorców Projektowych. Z moich obserwacji wynika, że kontekst ten jest rozpięty na 2-wymiarach: wspomnianej już klasie problemu oraz w wymiarze osobowości (otrzemy się o aspekty psychologii programistów).


KONTEKST KLASY PROBLEMU

Jeżeli jesteśmy w kontekście aplikacji biznesowych, a nasza praca polega na implementowania kolejnych wymagań, to stoją przed nami wyzwania typu:
- okiełzanie nielogicznych wymagań
- modelowanie narzędzia dla biznesu (naszej aplikacji), który działa z natury procesowo
- tworzenie intuicyjnego GUI
- wpasowanie się w istniejącą bazę danych, która była projektowana przed III Wojną Mandżulską, w czasach gdy nikt jeszcze tak na prawdę nie rozumiał domeny.

W tym kontekście często nie potrzebujemy nawet używania technik OO - aczkolwiek programujemy z użyciem obiektów (bo niby jak inaczej w mainstreamowych językach?).

Z szerokiego wachlarza Wzorców Projektowych możemy posłużyć się w tym kontekście - w pewnych bardziej złożonych - przypadkach:
- Strategią - jeżeli mamy odmiany tego samego zachowania/czynności biznesowej. Podejście promowane przez ten wzorzec staje się ostatnio modne za sprawą języków funkcyjnych. By czymże innym na poziomie syntaktycznym jest przekazanie obiektu implementującego interfejs z jedną metodą niż zwykłym przekazaniem funkcji na poziomie semantycznym?
- Łańcuchem Odpowiedzialności - łańcuch zaczyna się nam opłacać gdy chcemy modelować podejmowanie decyzji o tym, który z wariantów logiki biznesowej chcemy wykonać. Opłaca się gdy spodziewamy się modyfikowania ilości wariantów.
- Specyfikacją - pozwala na przejrzyste modelowanie złożonych reguł określających czy dany obiekt biznesowy powinien podlegać jakiemuś przetwarzaniu. Opłaca się gdy reguły są zawiłe i istnieją pomiędzy nimi zależności logiczne. Złożony wzorzec, że wystąpić kilka razy w całym kodzie.
- Stanem - bardzo złożony wzorzec, może wystąpić raz, może dwa w średniej wielkości systemie. Pozwala na przejrzyste modelowanie maszyny stanów, dla obiektów biznesowych, które mają np różne statusy i w zależności od statusu a) mogą lub nie przechodzi w inne oraz b) inaczej się zachowują. Opłaca się gdy mamy wiele statusów i zawiła logikę sterującą zachowaniem zależnym od statusów.
- Metodą Szablonową - prosty wzorzec, klasyka polimorfizmu, pozwala modelować różne warianty algorytmów biznesowych. Z czasem gdy sytuacja się komplikuje przestaje się opłacać i warto dokonać refaktoringu do Strategii.
- Fasadą - trywialny wzorzec, który nie zasługuje na miano wzorca, ale wygodnie upraszcza to co najpierw skomplikowaliśmy;)
- Dekoratorem - wprowadzenie dekoratora zaczyna się nam opłacać gdy chcemy !w runtime! (nie podczas programowania czy kompilacji) decydować o dodawaniu nowych zachowań. Bardzo silny, ale rzadko logika biznesowa daje się tak elegancko zamodelować.

Pomijam Singletona, który w większości przypadków jest antywzorcem zabijającym testowalność. Poza tym jego istnienie w modelu biznesowym rzadko bywa sensowne.

O ile pojawienie się w aplikacji biznesowej wielu przypadków wykorzystania Strategii, Metody Szablonowej, Fasady jest normalnym zjawiskiem o o tyle duża ilość Łańcuchów, Dekoratorów czy Stanów wskazuje na overengineering i "chorobę wzorcową".

Pozostałe wzorce z katalogu nie mają zbyt dużej szansy na potencjalne pojawienie się w aplikacji biznesowej - po prostu służą do rozwiązywania problemów innych klas.

W aplikacjach biznesowych za to więcej pożytku niosą wzorce innego rodzaju:
- sposoby modelowania, oraz strategicznego modelowa z zestawu Domain Driven Design
- archetypy modeli biznesowych
- najlepsze praktyki odnośnie Usability



Jeżeli natomiast jesteśmy w kontekście tworzenia oprogramowania klasy: platforma, framework, server, wysokowydajny dedykowany portal, oprogramowanie specjalistyczne typu: narzędzia do projektowania/komponowania, itp wówczas stajemy przed problemami zupełnie innej klasy:
- otwartość na rozbudowę z jednoczesną ochroną publicznego api przed zmianami core,
- wydajność, oszczędność pamięci,
- bezpieczeństwo,
- giętkość api,
- wyważenie ograniczeń
- itp.

Przeglądając kody źródłowe tego typu aplikacji możemy dostrzec wiele wystąpień wzorców lub struktur do nich podobny zmodyfikowanych z powodu pewnych intencji. Czasem są po prostu nazwane inaczej z powodu niewiedzy autora - ale ciekawe jest, że wpadł na pewne pomysły niezależnie, nie znając wzorca wcześniej.

Oprócz wspomnianych wcześniej wzorców opłacalne w tym kontekście dodatkowo mogą być:
- Builder - aby odseparować plany budowy obiektów od ostatecznego kształtu konstrukcji. Opłacalny gdy przewidujemy pojawianie się kolejnych "produktów"
- Visitor - aby umożliwić użytkownikom api wprowadzanie nowych zachowań poprzez dodawanie swojego kodu zamiast modyfikowania biblioteki/frameworka. Double Dispach dla ubogich np. w celu otwarcia core na rozbudowę
- Proxy - aby umozliwić model programistyczny w stylu Aspect Oriented
- Mediator - śmietnik na chaos sieci współpracujących obiektów. Jeżeli musisz mieć chaos, to explicite ogranicz jego zakres.
- Most - jeżeli masz niestabilny core i chcesz zaryzykować publikowanie stabilnego api
- Prototyp - jeżeli chcesz fabrykować wiele klas i nie chcesz tworzyć analogicznej do nich, lustrzanej hierarchii fabryk.
- Flyweight - jeżeli musisz mieć ogromną ilość obiektów, ale część ich pół możesz uwspólnić, ponieważ nie jest to ich zmienny stan - oszczędzasz pamięć.


Podając przykłady dla obu klas problemów chciałem pokazać, że po prostu inni programiści/projektanci/architekci mogą żyć w innym świecie. To że coś jest dla nas bezużytecznie, nie oznacza, je jest to bezużyteczne w ogólności.

Wrzucanie wszystkich programistów do jednego worka (najlepiej wspólnego worka z Informatykami;) powoduje utratę kontekstu a co za tym idzie rozmycie wymaganych do danego zadanie kompetencji.

Warto też zastanowić się nad klasami problemów jakie nas intrygują i rozwijać odpowiedni skillset - bez odpowiedniego poziomu kompetencji pewne klasy projektów będą dla nas zawsze zamknięte.



KONTEKST OSOBOWOŚCI

Chyba każdy programista przyzna, że zależy mu na zaprojektowaniu najprostszego rozwiązania. Być może to poszukiwanie prostoty jest tym, co przyciąga ludzi do programowania - no może za wyjątkiem robotników umysłowych napędzanych chęcią łatwego (hehe naiwni) zarobku.

Problem leży w postrzeganiu prostoty.
Niektórzy mówią, że jakieś podejście jest proste jeżeli można szybko zacząć się w tym orientować. I nie ważne, że później okaże się to pogmatwane w szczegółach. Ważne aby szybko zacząć "łapać" o co chodzi, a później - trudno, świat jest pogmatwany.
Inni mówią, że coś jest proste, jeżeli u swoich założeń ma dające się dokładnie zrozumieć fundamenty, które pozwalają wyobrazić sobie całe rozwiązanie mentalnie. Nie ważne, że może na początku trzeba zainwestować czas - dużo czasu - w zrozumienie pryncypiów i nowych rzeczy, które powierzchownie wydają się złożone. Jeżeli później okaże się, że było to proste, to mówią, że jest to proste.

Które podejście jest lepsze - jak zwykle, zależy to od kontekstu. W kontekście IT np. może zależeć od ograniczeń czasowych, czyli tego ile czasu możemy poświęcić. Innym ograniczeniem może być opłacalność w perspektywie czasu (czasem robimy eksperyment biznesowy i zobaczy jak się to rozwinie:). Dlatego w każdym przykładzie wzorca pisałem o tym kiedy może się nam opłacać wprowadzenie danej konstrukcji.

Strategia poznawcza, jaką przyjmuje nasz mózg w podejściu do prostoty (musi być proste od razu, musi być proste docelowo) wiąże się mocno z typem naszej osobowości.
- część z nas jest zorientowana na postrzeganie podobieństw, inni na postrzeganie różnic - model meta-programów .
- część z nas jest otwarta na nowe doświadczania, inni są bardziej konserwatywni - model Big 5.
- część z nas ma swój wewnętrzny świat, w którym modeluje postrzeganą rzeczywistość, inni są nastawieni na natychmiastowe działanie i niemal namacalne doświadczanie - model MBTI
- część z nas żyje w przyszłości, w jakimś jej modelu, inni żyją tu i teraz w czasie.


MODEL DREYFUS


O modelu kompetencji Braci Dreyfus pisałem już kilkakrotnie. Tutaj przytoczę tylko poziom Competent. Rozwijając jakąś umiejętność, na tym poziomie w naturalny sposób zaczynamy szukać najlepszych praktyk, wzorców (nomen omen). Tak więc naturalne jest, że rozwijając kompetencję pod tytułem "Programowanie/Projektowanie OO" w pewnym momencie zaczniemy szukać wzorców.
Na kolejnych etapach rozwoju tej kompetencji będziemy zadawać pytanie, kwestionować, szukać szerzej. Jednak na trzecim poziomie (Competent) katalog wzorców daje dużego kopa i pozwala po prostu zoptymalizować proces uczenia się. Jak powiedział Jim Copelin, z czasem odrzucasz "boczne kółka od swego rowerka" i przestajesz mówić o wzorcach, po prostu intuicyjnie robisz to co należy.


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

Czasem można spotkać głosy, że wzorce projektowe są "zbyt akademickie". Hmm moim zdaniem wręcz odwrotnie. Do katalogu wzorców nie trafiają żądne rozwiązania, które nie są odkryte i przedyskutowane przez community praktyków. Oczywiście, stosując zbyt wyrafinowany wzorzec do zbyt błahego problemu będziemy odczuwać przerost formy nad treścią. Ale wówczas powodem jest nieumiejętne użycie w nieodpowiednim kontekście - czyli jego niezrozumienie.

Aby nabrać wyobrażenia o tym, co to znaczy "akademickie" podejście do OO należy zapoznać się z tym tekstem.

piątek, 4 lutego 2011

keepaliver.ping()

Sporo czasu minęło od ostatniego posta. Powód jak zwykle ten sam - zgodnie ze starym chińskim przysłowiem: "Jest czas na pracę i jest czas na zbieranie ryżu".

Ale mimo nawału pracy mam dla Was trochę materiałów:

1. Prezentacja "Software Craftsmanship - Język wzorców językiem profesjonalistów"
Dzięki uprzejmości Łukasza Lenarta na parleys pojawiła się moja prezentacja z zeszłorocznej javarsowi.

2. Prezentacja "Domain Driven Design. Wszystko jest na miejscu i wszystko ma swoje miejsce".
Najbliższy tydzień spędzam we Wrocławiu. Przy okazji, na zaproszenie Pawła Zubkiewicza - lidera wrocławskiego JUGa - opowiem o DDD; szczegóły.
Z braku czasu będzie to ta sama prezentacja, którą przedstawiałem na zeszłorocznym NYAC. Slajdy będą te same, ale od czerwca zgromadziłem nowe doświadczenia, o których będę chciał opowiedzieć.


Natomiast w kwietniu szykuję dla Was nowy materiał - prezentację "Command-query Responsibility Segregation - nowe, bardziej racjonalne podejście do warstw". Mam ambitny plan aby forma graficzna tej prezentacji była mocno odmienna od wszystkiego co do tej pory pokazywałem.

Materiał będzie można zobaczyć na konferencjach:
- 33rd Degree
- 4 Developers

Zapraszam serdecznie na obie konferencje. Myślę, że agenda 33rd Degree już teraz wygląda bardzo zachęcająco.
Natomiast jako członek komitetu programowego 4 Developers zdradzę, że również będzie ciekawie:)

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

Kolejne posty merytoryczne powinny pojawić się pod koniec miesiąca.
Chociaż przyznam, że Java i inżynieria oprogramowania muszą konkurować o zasoby z moją nową zabawką - audiofilskim zestawem stereo, który trzeba co chwilę testować na nowych kablach, płytach i w nowych rozmieszczeniach (ostrzegam - nie dajcie się w to wciągnąć) ;P

środa, 28 kwietnia 2010

Majowa ewangelizacja Software Craftsmanship


Prezentacja poświęcona tematyce Software Craftsmanship została dosyć ciepło przyjęta podczas poznańskiego 4Developers, oraz spotkała się z niemałym odzewem w community.

Dlatego uznałem, że warto kontynuować propagowanie profesji w naszej branży - szczególnie, że nasze community zaczyna coraz wyraźniej sygnalizować pewne problemy.

Zapraszam zatem wszystkich zainteresowanych tematyką Software Craftsmanship na szereg darmowych konferencji, gdzie planuję wystąpienie:

- lubelskie dni IT - 12 maja w Lublinie
- Infoshare - 14 maja w Gdańsku
- Javarsovia - 26 czerwca w Warszawie

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

Cieszy fakt, ze tematyka Craftsmanship pojawia się również na JUGach: warszawskim i wrocławskim

wtorek, 23 marca 2010

Koszt wykorzystania wzorców projektowych

Podczas dzisiejszej prezentacji na temat Software Craftsmanship i Wzorców Projektowych, którą przeprowadziłem w ramach lubelskiego JUG padło szereg pytań odnośnie:
- kosztu posługiwania się w kodzie wzorcami
- presji czasu
- komplikacji

Jest to pewien ogólny i często powtarzający się zestaw pytań, dlatego sądzę, że warto rozwiać kilka wątpliwości i nieporozumień.

Część programistów widzi wykorzystanie wzorca jako niepotrzebną komplikację, która wprowadza dodatkowy narzut na wytworzenie kodu.

Generalnie jeżeli chodzi o poziom komplikacji to wzorce powinny redukować złożoność przypadkową oraz zwiększać czytelność złożoności właściwej. Jeżeli mamy sytuację odwrotną to wzorzec został wykorzystany niepoprawnie lub niepotrzebnie.

Odnośnie narzutu czasowego to obserwuję dziwne przeświadczenie jakoby dopisanie interfejsu czy kilku linijek kodu potrzebnych na podzielenie kodu na większą ilość pudełek (klas) było jakoś niezwykle czasochłonne;) Sam proces myślowy również nie powinien się znacząco wydłużyć. Albo wiem jaki wzorzec użyć albo nie wiem. To nie jest tak, że zastanawiam się nad tym godzinami.

Warto zwrócić uwagę na fakt, że katalog Wzorców Projektowych Ogólnego Przeznaczenia jest dosyć bogaty.

Owszem, część z nich jest dosyć wyrafinowana pod względem koncepcyjnym lub pod względem konstrukcji kodu. Przykładowo taki Visitor, Component Tree, Bridge, Proxy, Builder, Flyweight, Memento prowadzają pewien narzut, ale są to narzędzia, które wybieramy do misji specjalnych. Na pewno nie należą do codziennego wachlarza technik.

Natomiast Strategia, Stan, Dekorator, Template Method, Command to lekkie "skróty myślowe" wynikające wprost w paradygmatu Object Oriented. Nie ma w nich doprawdy nic nadzwyczajnego.

Osobiście traktuję część z nich jako "atomowe klocki", z których powinno budować się rozwiązania. Są to byty niemal tej samej klasy ciężaru co IFy, pętele czy metody. Biegłe opanowanie lekkich wzorców sprawia, że poszerzają one nasz zestaw podstawowych "klocków myślowych". Budujemy z nich najpierw mentalne modele, a później naturalnie pojawiają się w kodzie.

Operując lekkimi wzorcami na poziomie podstawowych klocków nie mamy narzutu czasowego podczas developmentu. Natomiast poziom komplikacji ulega znacznemu obniżeniu, ale tylko dla tych czytelników kodu, który posiadają taki sam zasób skojarzeń jak jego twórca - czyli operują tym samym słownictwem branżowym.

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

Potrzeba posiłkowania się wzorcami "lekkiego" kalibru wynika ze słabej siły wyrazu języków wywodzących się z C++ takich jak np Java. Przykładowo brak domknięć kompensujemy sobie strategiami. Doprawdy nie jest niczym strasznym i pracochłonnym opracowanie interfejsu z jedną metodą oraz kilku jej implementacji. Ilość kodu "biznesowego" nie ulega zwiększeniu (ew redukcji) natomiast pojawiają się jedynie dodatkowe "opakowania" dla niego w postaci zgrabnych i eleganckich klas.

Jeżeli ktoś aż tak bardzo nie pała chęcią do pisania kodu to może powinien zostać trubadurem;)

Ciężki kaliber wzorców również może czasem przydać się do emulacji brakujących elementów składni, takich jak:
- double dispatch emulowany przez wzorzec Visitor
- lazy evaluation emulowany przez wzorzec Proxy.

niedziela, 7 lutego 2010

Inżynieria w służbie użyteczności

/*
 *Tytuł niczym wzięty ze sztandaru
 *z jakiegoś pochodu
 *z uprzedniej smutnej epoki:)
 */

Witam po długiej przerwie. Zaprezentuję dziś pewne ujęcie Usability oraz koncepcję technicznego rozwiązania użytecznej aplikacji - jej architekturę i dwa główne wzorce projektowe.

NOWICJUSZ vs EKSPERT
Użyteczność niejedno ma oblicze. Możemy mówić o przejrzystości, intuicyjności, ergonomii itd. Każdy z nas zapewne zdefiniuje dobre GUI jako takie, które oferuje to czego się spodziewamy. Ale co to znaczy? Oczywiście dla każdego coś innego.

Dziś skupimy się na problemie różnych potrzeb i oczekiwań użytkowników o różnym poziomie zaawansowania.

Analizując Model Rozwoju Kompetencji Braci Dreyfus
krótki opis
szerszy opis
możemy wywnioskować, że użytkownicy reprezentujący skrajne poziomy kompetencji potrzebują skrajnie odmiennego GUI.

Nowicjusz i Zaawansowany Początkujący są nastawieni na zadania ponieważ nie ogarniają jeszcze całości problemu. Potrzebują określonej ścieżki, przez którą będą prowadzeni za rękę a na każdym etapie dostaną jasne wytyczne odnośnie tego CO trzeba zrobić.

Z drugiej zaś strony Kompetentny, Biegły i Ekspert są nastawieni na osiągnięcie celu i mniej lub bardziej orientują się jak go osiągnąć.

Jak to w życiu bywa: "różni ludzie, różne potrzeby".

Mniej zaawansowani użytkownicy potrzebują aplikacji-asystenta zorientowanej na zadania. Aplikacja taka nie powinna atakować mnogością możliwości. Powinna w danym momencie sugerować (lub wręcz zmuszać) do zrobienia właściwej rzeczy. Przykładem mogą być nasze ulubione kretynatory (yyy kreatory).

Zaawansowany użytkownik potrzebuje aplikacji zorientowanej na możliwości. Dla niego wygodne będzie rozbudowane menu, bez sugerowania co kiedy kliknąć. Zaawansowany użytkownik dobrze wie co chce zrobić i nie potrzebuje aby mu podpowiadać. Mało tego, taki użytkownik ma wyrobiony swój własny styl pracy, zatem aplikacja-asystent jedynie przeszkadza. Najlepszym przykładem może być: IDE (np. Eclipse), zaawansowany edytor tekstu albo narzędzie typu CAD. Tysiące opcji w menu i biała kartka na dzień dobry.


Klasyczny problem występujący w aplikacjach biznesowych to nauka obsługi użytkowników. Stare systemy tej klasy orientowało się na możliwości (teraz powoli sytuacja ulega zmianie). Mamy zatem nieszczęsną użytkowniczkę, która zaczyna naukę obsługi systemu (może również jednocześnie "uczy się komputera"). Przytłoczenie mnogością opcji skutkuje jednym: tresurą. Użytkownik nie ma innego wyjścia jak opanowanie procesu biznesowego na zasadzie szympansa: klikam trzeci z lewej, później ok, później przedostatnia zakładka, później klikam drugi od dołu...

Wszyscy znamy też nietrafione rozwiązanie, które miało pomagać nowicjuszom w okiełzaniu eksperckiego narzędzia - spinacz w Office;)

PRZYKŁAD APLIKACJI
W przypadku aplikacji korporacyjnych jest łatwiej. Użytkownik nie ma innego wyjścia jak zacisnąć zęby i nauczyć się (pardon - wytresować się). Jest to po prostu przykre narzędzie jego pracy.

Natomiast gdy nasza aplikacja ma konkurować na wolnym rynku wówczas musimy nieco się wysilić. Użytkownik zirytowany autystycznym GUI aplikacji po prostu porzuci ją, zaklnie i pójdzie sobie do konkurencji:)

Użytkownicy są teraz niezbyt cierpliwi i mają wysokie wymagania odnośnie przyjemności - pokolenie MTV?


Aby ustalić kontekst aplikacji walczącej o klienta wysokim poziomem użyteczności wyobraźmy sobie system do projektowania wnętrz, np. wnętrz kuchni.
Niech zaprojektowanie wymaga między innymi: określenia obszaru pomieszczenia, ustalenia otworów i innych przeszkód, rozstawienia mebli i AGD, dopasowania wszystkich wymiarów, wyboru materiałów z których mają być wykonanie poszczególne elementy, itd. Dalej mamy rysunki techniczne, wyliczenia należności, umowy, itp.

Ustalmy wyzwanie: z aplikacji ma być wykorzystywana przez:
- klientów końcowych, którzy to raz na kilka(naście) lat kupują sobie "kuchnię"
- projektantów kuchni, którzy dziennie wyklikują po kilka takich zestawów.

Co z kompetencjami naszych użytkowników?
Jasne jest, że klient końcowy jest totalnym Nowicjuszem jeżeli chodzi o aplikację. Użyje jej raz (ew jako rodzinny "haker" pomoże szwagrowi wyklikać jego zestaw). Zatem nigdy nie nabędzie wyższych kompetencji. Co gorsza klient może być również niekompetentny w domenie problemu, czyli wszystkich zagadnieniach związanych z kuchniami.
Manual oczywiście odpada, bo zirytowany klient pójdzie sobie do konkurencji.

Projektant jest zwykle w miarę kompetentny w domenie problemu;) Będzie też Nowicjuszem jako użytkownik, ale ponieważ będzie miał motywację i częstą styczność z aplikacją to szybko osiągnie poziom Kompetentny a z czasem dojdzie do Eksperta.

ARCHITEKTURA ROZWIĄZANIA
Mamy zatem dwie klasy użytkowników, którzy potrzebują diametralnie różnego interfejsu. Początkujący użytkownik wymaga przeprowadzenia go przez cały proces projektowania krok po kroku, bez zbędnych pytań, defalutowo, szybko, gładko i bez irytacji. Zaawansowany użytkownik potrzebuje natomiast wolności, możliwości i tysięcy szczegółowych opcji.

Jak to pogodzić? Czy musimy tworzyć niemal 2 osobne aplikacje?
Niekoniecznie, jeżeli podejdziemy do problemu racjonalnie to możemy zredukować narzut związany z przygotowaniem 2 wersji.

Możemy podejść do problemu następująco:

Wydzielamy CORE aplikacji, który zawiera klasy odpowiedzialne za główną funkcjonalność, czyli projektowanie wnętrz (wraz ze wszystkimi regułami z tym związanymi) oraz wizualizację projektu.
Core jest dostępny przez API.

Mamy 2 zestawy GUI: jeden prosty i intuicyjny oraz zorientowany na zadania przeznaczony dla początkujących. Drugi to klasyczny potwór dla eksperta oferujący niezliczone możliwości w rozbudowanych menu.

Oba rodzaje GUI komunikują cię z Corem wysyłając polecenia zhermetyzowane przy użyciu wzorca projektowego Command. Idea wzorca jest taka, że czynności nie są metodami (jak w klasycznym podejściu proceduralnym). Czynności są obiektami. Dla wszystkich klas poleceń istnieje wspólny interfejs zawierający jedną metodę - jest to sygnał do polecenia aby wykonało ono zawartą w sobie logikę.

Konkretny command zawiera w sobie zestaw instrukcji operujących na API Cora. Oczywiście commandy wysyłane z GUI Nowicjusza będą zawierać więcej kodu operującego na API. Jedna czynność takiego użytkownika wykonuje więcej operacji. Commandy wysyłane z GUI Eksperta będą bardziej elementarne. Commandy mogą po sobie dziedziczyć, mogą się na wzajem dekorować z wykorzystaniem wzorca Dekratora w celu reużycia logiki.

W przedstawionej architekturze mamy dodatkowo wzorzec Mediatora. Został on wprowadzony jako "środowisko uruchomieniowe" dla poleceń oraz "hub" spinający wiele komponentów. GUI wysyła commadny do Mediatora, a to mediator wykonuje otrzymane polecenie (uruchamiając wspólną metodę interfejsu) przekazując do nich kontekst uruchomienia, czyli core.
Mediator może również przy okazji manipulować stanem GUI, które nie należy do Core (np nakładać przeźroczystą chmurę na czas długotrwałych operacji bądź włączać dostępność opcji w menu). Mediator może też generować zdarzenia aplikacyjne, które będą obsługiwane przez pewne komponenty graficzne prezentujące zajście danego zdarzenia. Można bawić się do woli z Mediatorem, ale trzeba uważać aby nie stał się boską klasą o zbyt dużej odpowiedzialności.

ZWIĘKSZAMY USABILITY
Każdy użyteczny system, którego idea zasadzą się na bogatej interakcji użytkownika powinien oferować funkcjonalność "Cofnij". Wprowadzenie tej funkcjonalności jest generalnie bardzo kłopotliwe. Kto zmagał się z tym problemem ten zapewne próbował niewydajnego rozwiązania polegającego na "zrzucie" aktualnego stanu do pamięci podczas wykonywania każdej operacji modyfikującej stan.

Ogólnie jest to problematyczna funkcjonalność, chyba że...


...posiłkujemy się wzorcem Command:)

Możemy wówczas rozszerzyć go o anti-command.
Każde polecenie zawierające logikę zawiera w sobie anty polecenie (jako obiekt dostępny przez getter - a getter w interfejsie Command). Antypolecenie zawiera logikę odwrotną do polecenie.

Teraz w naszym przypadku Mediator wykonując polecenie, pobiera z niego anty-polecenie i odkłada je na stosie. W razie potrzeby, czyli gdy użytkownik kliknie cofnij (ctrl+z) wołamy Mediatora, który pobierze polecenie ze stosu anty-poleceń i wykonana go. Proste:)


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

O ile omawiana problematyka usability w kontekście kompetencji użytkownika jest ogólna, to przedstawione rozwiązanie nie aplikuje się do aplikacji webowych z cienkim klientem. Zresztą taki klient ma zastosowanie do innej klasy problemów.

Natomiast rosnąca popularność grubszych klientów (opartych o: flex, javafx, silverligh, stare-dobre-niedoinwestowane-przez-matołów-suna applety czy nawet GWT) pozwala przewidywać, że coraz częściej będziemy spotykać się problemami tej klasy.

piątek, 16 października 2009

MVC Revisited

Kto w 2009r pisze jeszcze o MVC? Czy można jeszcze coś konstruktywnego dodać w tym temacie?

Spróbuję napisać coś może nie tyle konstruktywnego co porządkującego pojęcia... Doczytajcie do końca a być może dowiedzcie się czegoś co uzupełni wasz zasób ciekawostek służących do robienia wrażenia np na niewiastach podczas imprez;)

MVC - styl to architektoniczny odkryty bodajże jakieś 30 lat temu. Zatem należy mu się najpierw rys historyczny: jak to drzewiej wyglądało w Smalltalku...
Widok - czysty rendering
Kontroler - obsługa zdarzeń urządzeń sprzęgu ludzkiego (taki się kiedyś mówiło) typu klik myszką czy naciśnięcie klawisza
Model - dane do prezentacji i logika nimi manipulująca.

Jak to wygląda dziś? Weźmy na ten przykład 2 popularne technologie prezentacji w Javie:

SWING
Czym są kontrolki w Swingu... Widokiem? Nie do końca, ponieważ delegują rendering do rendererów. Sam np JButton jest reprezentacją jakiegoś bytu na GUI - posiada atrybuty typu położenie, ale rysowaniem zajmują się renderery wchodzące w skład aktualnego look&feel. Ale może nie wnikajmy w implementację i dla uproszczenie niech będzie to widok.

Kontrolki mają również swój wewnętrzny model "prezentacyjny". SetText przecież ustawia "coś" we wnętrzu kontrolki. Zwykle kopiujemy to coś z modelu właściwego budując własny framelet (nano framework) wokół Swinga. Ale sam rendering następuje na podstawie wewnętrznego modelu kontrolki.

Przyjrzyjmy się obsłudze zdarzeń... W Smaltalku zdarzenia techniczne obsługiwał kontroler. W Swingu robi to kontrolka, która deleguje odpowiedzialność do listenerów. Czyli to listenery są kontrolerem? Nie zawsze... w niektórych frameworkach Swingowych implementujemy własne kontrolery wołane przez listenery.

Więc co z tym MVC? Wygląda na to, że to kontrolka jako taka implementuje MVC (patrząc z technicznego punktu widzenia)

JSF
W JSF mamy Managed Beans... Obsługują one zdarzenia oraz decydują o nawigacji - czyli kontroler. Zawierają model do renderingu i zbierania danych z formularzy.

A co będzie, jeżeli zbindujemy jakieś kontrolki graficzne z ich komponentami UIComponent (będącymi polami Managed Beana)? Wówczas MB może sterować atrybutami kontrolek graficznych, czyli mamy logikę prezentacji, czyli widok. Znowu wszystko w jednym.


SZERSZY KONTEKST
Aby tego było mało spójrzmy na listę istniejących reifikacji (koncepcji realizacji) idei MVC zgromadzoną w tym oto poście: Interactive Application Architecture Patterns.

Na liście wiele ciekawych pozycji:
- Model-View-Presenter - w podejściu Microsoft (+ Application Controller) oraz Dolphin
- Supervising Controller
- Passive View Pattern
- Presentation-Abstraction-Control

Sam dodatkowo natknąłem się na:
- Hierarchical MVC (framework Scope, który przez parę miesięcy rozwijałem niezależnie od twórców, którzy porzucili coś tak pięknego;)
- MMVC (rozróżnienie na model prezentacyjny + model domenowy)


Wszystkie wymienione podejścia nazwami nawiązują do MVC, ale nim nie są w formie oryginalnej. Czy to źle? Oczywiście, że nie - MVC w swej oryginalnej formie nie przystaje do konstrukcji współczesnych bibliotek i zakładanego przez nie poziomu abstrakcji. Kto np dziś skupiałby się na szczegółach technicznych obsługi zdarzeń generowanych przez urządzenia.


WARSTWY
MVC jest często mylony z architekturą warstwową. Wystarczy zrobić prosty eksperyment myslowy aby uświadomić sobie błąd: zwizualizujmy sobie oba podejścia:
MVC wygląda jak trójkąt.
Natomiast warstwy są jak Ogry;) Albo raczej jak kanapka, tudzież tort.

MVC może być (i zwykle jest) użyty na szczycie stosu warstw jako wzorzec dla samej warstwy prezentacji. Natomiast pod spodem mamy "zwykłe" warstwy, które nic nie wiedzą o żadnym MVC w prezentacji.

W literaturze spotyka się jednak uproszczone podejścia, gdzie składniki trójkąta MVC utożsamiane są z warstwową architekturą, odpowiednio:
V - warstwa prezentacji
M - warstwa modelu (anemicznego najlpiej)
C - warstwa logiki aplikacji
Mówi się wówczas, że MVC jest traktowany jako wzorzec architektury aplikacji a nie jako wzorzec projektowy prezentacji.
Niech i tak będzie... ;)
Byle tylko nie twierdzić, że widok to HTML + CSS, kontroler to PHP a model to baza danych;P

//=================
Post powstał tak a'propos dzisiejszej prezentacji Marka Richardsa na JDD podczas, której wspomniał on o bezmyślnie przyjmowanych dogmatach:)

wtorek, 6 października 2009

Domain Specific Query

Zgodnie z obowiązującym trendem polegającym na prześciganiu się w publikowaniu bardziej lub jeszcze_bardziej oczywistych odkryć - dziś kolej na mnie i na coś na co wpadłem już jakiś czas temu.

Tytułowy wynalazek ma roboczą nazwę DSQ, którą triumfalnie nadałem mu kojarząc dwie modne techniki: Domain Specific Language i Command-query Separation.

NIEKRÓTKIE WYJAŚNIENIE
O CQS pisałem 2 miesiące temu w >tym< poście. W skrócie jest to styl architektoniczny, który zakłada separację API systemu na dwie wyraźne części. Jedna obsługuje (przyjmuje wysyłane do API) Command - rozkazy wykonania operacji. Druga natomiast obsługuje Query - obiekty specyfikujące kryteria pobierania/wyszukiwania danych.

W dzisiejszym poście skupimy się właśnie na tej drugiej części, czyli ogólnie mówiąc na wyszukiwarkach danych. Wyszukiwarki są dosyć proste - ot zwykłe servisy, pod którymi nie znajdują się już żadne kolejne warstwy - chyba, że potrzebujemy jeszcze "dodać coś od siebie" do pobranych z pewnego źródła danych.


DSL to koncepcja dużo bardziej popularna (w sensie ilości publikacji) niż CQS, więc nie będę się zbytnio rozpisywał na jej temat. Ogólnie rzecz ujmując chodzi o to aby kod logiki biznesowej czytać niemal jak język naturalny. Definiujemy sobie język zorientowany na konkretną domenę biznesową a później to już fraszka - program piszę się niemal sam;)
W statycznych językach typu Java nie mamy zbyt szerokiego pola do popisu jeżeli chodzi o zdefiniowanie własnego języka domenowego.

Jedyne co możemy zrobić w Javie to zastosować Fluent Interface i opisowe (samoopisujące wręcz) nazwy metod. Czyli jest to jedynie namiastka DSL, ale cieszy:)

Koncepcja Fluent Interface jest prosta: chodzi o to aby metody danej klasy zwracały instancję obiektu na rzecz którego zostały wykonane. Dzięki temu możemy zamiast pisać tradycyjnie:

Klasa obiekt = new Klasa();
obiekt.metoda1();
obiekt.metoda2();
obiekt.metoda3();

możemy zmienić styl na bardziej fluent:

Klasa obiekt = new Klasa();
obiekt.metoda1().metoda2().metoda3();

Jeżeli teraz zamiast nadawać metodom autystyczne nazwy (w stylu Bruce Eckela), wykażemy się odrobiną inwencji i słowiańskiej fantazji możemy pisać niemalże poetycko:

FabrykaWierszy.stworzUtwor().subtelny().zNutkaDekadencji().rymyNiechBedaCzestochowskie().strof(10);

Chyba lepiej czyta się kod w takim stylu...


DO RZECZY
Po tym przydługim wstępie powinniśmy już mniej więcej czuć czym jest CQS i DSL...

Wyobraźmy sobie następujący problem :

W systemie przechowujemy dokumenty. Dokumenty mają szereg atrybutów: kilka typów statusów (związanych z przepływem dok. jak i technicznych), różnego rodzaju daty, przypisanie do różnych zasobów itp.

Domena biznesowa standardowa i może niezbyt wyszukana, ale dzięki temu wiadomo o co chodzi i możemy skupić się na samej technice.

Serwis, który miałby wyszukiwać dokumenty musiałby mieć metodę o kilkunastu parametrach. Pierwszym problem z taką metodą to wysoki ujemny współczynnik czytalności. Drugi problem to ciągłe zmiany sygnatury metody gdy pojawiają się nowe atrybuty dokumentu.

Zatem dosyć oczywiste jest, że stworzymy sobie klasę przechowującą atrybuty wyszukiwania a nasz serwis będzie miał metodę, do której przekazujemy paczkę kryteriów - po prostu jakiś rodzaj Query z CQS:


public class SearchDocumentsQuery implements Serializable{
//atrybuty wyszukiwania + get/set
}



public class DocumentsFinder{
public Collection<Document> search(SearchDocumentsQuery criteria){
//..
}


Jak do tej pory standard. (Query najpewniej będzie prędzej czy później serializowany.)

Teraz idąc dalej wg utartych szablonów dodalibyśmy do SearchDocumentsQuery kilkanaście pół prywatnych oraz oczywiście nasze ulubione gettery i settery.

Kod klasy klienckiej przy pomocy setterów ustawia kryteria wyszukiwania a kod metody DocumentsFinder.search przy pomocy getterów pobiera te parametry i klei sobie zapytanie w SQL, HQL tudzież mój ulubiony Hibernate Criteria API.

A gdzie miejsce dla DSL? No więc właśnie przy tak ordynarnym podejściu nie uświadczymy.
Załóżmy teraz, że chcemy hermetyzować kod klas klienckich (wywołujących DocumentsFinder)od szczegółów atrybutów Dokumentu.

Klient zamiast operować szczegółami:

SearchDocumentsQuery criteria = new SearchDocumentsQuery();
criteria.setStatus(Status.ACTIVE);
criteria.setCreateDate(...)
criteria.set...
...

Collection<Document> result = documentsFinder.search(criteria);


Wyraża jedynie intencje co do chęci otrzymania dokumentów spełniających zestaw określonych specyfikacji biznesowych:

SearchDocumentsQuery criteria = new SearchDocumentsQuery();
criteria.current().contains("lorem ipsum");

Collection<Document> result = documentsFinder.search(criteria);


Gdzie przykładowo metody DSL wyglądają mniej więcej tak:


public class SearchDocumentsQuery implements Serializable{
private Status status;
private Date epiryDate;
private String[] titleWords;
private String[] contentWords;

public SearchDocumentsQuery current(){
status = Status.ACTIVE;
expiryDate = //data jutrzejsza
return this;
}

public SearchDocumentsQuery contains(String phrase){
String[] words = phrase.split(" ");//lub bardziej wyrafinowy split
titleWords = words;
contentWords = words;
return this;
}

}


Jak interpretować powyższe metody ustawiające kryteria:
current - oznacza, ze życzymy sobie dokumentów będących aktualnie w obiegu.
Co to znaczy od strony modelu: że maja określony status i że wygasają np najwcześniej jutro.

contains - oznacza, że dokument ma zawierać określoną frazę. Z punktu widzenia modelu oznacza to, że tytuł lub treść dokumentu zawiera wszystkie słowa frazy.

Cała idea polega na tym, że klient nie wie nic o polach status, expiryDate, words, ...
Klient wyraża intencję.

Natomiast pola te są dostępne przez gettery dla DocumentsFinder, który to analizuje je i odpowiedzi składa zapytanie. Aby osiągnąć pożądaną widoczność getterów należałoby zdefiniować SearchDocumentsQuery jako klasę statyczną wewnętrzną w DocumentsFinder. Wówczas DocumentsFinder będzie widział metody prywatne, bo takie powinny być rozważane gettery. Statyczną ponieważ chcemy tworzyć Query bez "wywodzenia" jej z instancji Findera.



public class DocumentsFinder{

public static class SearchDocumentsQuery implements Serializable{
private Status status;
private Date epiryDate;
private String[] titleWords;
private String[] contentWords;

//metoda contains i current - pominięto

private Status getStatus(){
return status;
}
}

public Collection<Document> search(SearchDocumentsQuery criteria){
if (criteria.getStatus() != null)//wywołanie prywatnej metody
//dołożenie do zapytania warunku na status
...
}
}



Kuszące może wydawać się dodanie do SearchDocumentsQuery metody buildEecutable(), która produkuje na podstawie stanu swoich wewnętrznych pól (statys, words,...) coś co można wykonać. Tym "czymś" może być PreparedStatement z JDBC, Criteria z Criteria API Hibenrate, Query z JPA, czy zwykły String zawierający SQL albo HQL.

Ale zastanówmy się: czy klasa, która jest widoczna przez klienta (np zdalnego) powinna mieć w sobie kod, który operuje na jakimś API np Hibernate (Criteria API) albo czy powinna wiedzieć coś o jakimś SQL? Przecież Finder może szukać w XMLu lub poprzez web service. Klient nie może tego wiedzieć - ba nie powinien.

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

Pomysł może mało odkrywczy i zapewne wiele osób wpadło na niego równolegle, ale jeszcze nie spotkałem nigdzie opisu.

Pomysł oczywiście nie będzie miał zastosowania w aplikacjach z małą abstrakcją pomiędzy bazą danych a GUI - tak zwanych przeglądarkach danych. W tego typu aplikacjach kontrolki na GUI (w tym pola filtrujące) odpowiadają zwykle niemal dosłownie kolumnom w bazie. W tego typu przypadkach próba abstrahowania od parametrów modelu jest niepotrzebnym uprawianiem dyscypliny pod tytułem nakładanie gaci przez głowę;)

Czekam na opinie, sugestie, krytykę - może być niekonstruktywna:)

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/

niedziela, 21 września 2008

KomplikatorMode.ON

Gdyby ktoś z Was wpadł na genialny pomysł systemu podgrzewania rączek w rowerze to ostrzegam: nie ma on szans na komercyjny sukces w starciu z rękawiczkami!

Sam często łapię się na nieświadomym przełączeniu w tryb komplikatora. Szczególnie patrząc retrospektywnie na własne "dzieła". Jedynym pocieszeniem jest fakt iż istnieją więksi durnie;)

Czym jest działanie mózgu w trybie komplikatora? Jest to szukanie problemu w samym rozwiązaniu jakiegoś problemu. Czyli wyszukiwanie sobie meta-problemu - innymi słowy sztucznego problemu, tudzież dziury w całym.

Czym może się to objawiać?
Na poziomie architektury na ten przykład możemy popaść w paranoję i dążyć do unikania zależności od frameworków. W prostych/małych projektach należy się zdecydować. Albo opieramy się na jakimś frameworku albo nie. Jeżeli tak, to nie należy z nim walczyć. To bez sensu. Lepiej go wchłonąć i korzystać z wszystkich jego dobrodziejstw zamiast blokować sobie dostęp do ficzerów frameworka. W przeciwnym wypadku dochodzimy do paranoi pod tytułem Inner Platform Effect. Syndrom ten może przybrać ekstremalną formę od tytułem Not Invented Here.

Na czym może to polegać? Przykładowo na dodawaniu warstw abstrakcji w celu przykrycia API frameworka czy biblioteki. Idea jest szczytna: jesteśmy niezależni więc możemy dowolnie podmienić sobie implementację np warstwy dostępu do danych, warstwę prezentacji czy silnika reguł. Wystarczy pomyśleć racjonalnie (w kontekście przykładu dostępu do danych): czy kiedykolwiek wymienimy JPA (czy nawet Hibernate) na coś innego? Owszem fajnie jest mieć potencjał do testowania jednostkowego. Ale jakiego testowania?:P
Z czasem kończy się to tak, że nasza "fasada" powiela większość funkcjonalności frameworka - niestety powiela w sposób nieudolny bo jak tu przebić kilkudziesięcioosobowe community ekspertów;P

<nudny_przyklad_tylko_dla_uzytkownikow_hibernate>
Jako konkretny przykład, który często się pojawia (i nad którym sam mogę gorzko zapłakać) można podać doskonałe Criteria API z Hibernate. W prostych projektach aż się prosi aby Detached Criteria napełniać na poziomie GUI ekranu jakiegoś filtrowania. Niestety lęk przed couplingiem prezentacji z Hibernate prowadzi do tworzenia sztucznych protez (DTO) przenoszących kryteria wyszukiwania z GUI do DAO. W DAO mamy tłumaczenie protez na Criteria API.

Z czasem gdy komplikacja rośnie wprowadzamy wzorzec iterpretera (nieudolnie powielając API Hibernate:) przy pomocy którego implementujemy w DTO algorytmy już zawarte w Criteria API.

No ale dzięki temu zawsze możemy podmienić sobie framework persystencji - nawet dwa razy;)
</nudny_przyklad_tylko_dla_uzytkownikow_hibernate>


Na poziomie projektu natomiast możemy przesadzić np Wzorcami Projektowymi. Jest nawet takie schorzenie - zapadają na niego młodzi adepci, którzy się wzorcami zachłysnęli. Ciężko z niego wyjść. Cholernie ciężko... Wiem coś o tym:)
Często lepiej projektować w kierunku wzorca. Polega to na zostawianiu sobie możliwość rozbudowy w razie potrzeby do danego wzorce jednak jeszcze nie wprowadzając niepotrzebnej komplikacji.



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



My - programiści jesteśmy z natury umysłami ścisłymi (oby nie ciasnymi) - mamy więc niejako wbudowany detektor prostoty. Poszukujemy prostoty ponieważ jest piękna. Tak, wiem, to wyświechtany ogólnik - chodzi o coś głębszego: prosta forma zwalnia percepcję z zajmowania się rozpraszającymi detalami. Najlepiej gdy nie ma już czego odjąć niż nie miałoby być czego jeszcze dodać. I to właśnie pozwala dostrzec piękno.

Aby nie było tak prosto, jest jeden skomplikowany problem: należy umieć odróżnić rozwiązanie proste od prostackiego. Zgodnie z maksymą Einteina „Rzeczy powinny być tak proste jak to możliwe, ale nie prostsze”. Rozwiązanie proste nie zawsze jest nachalne i oczywiste. Często potrzeba wiele trudu i pracy mentalnej aby do niego dojść. Proste rozwiązanie może wymagać wypracowania poprzez wielokrotne próby i błędy. Przepraszam, nie ma błędów, są tylko rezultaty o niesprzyjającym wyniku:)

piątek, 19 września 2008

Nawróćcie się;)

Dzisiaj mały post (właściwie to wypychacz bo zbieram materiały na 2 większe) z serii ciekawych linków.
Allen Holub jest niestrudzonym ewangelizatorem Object Oriented i wzorców projektowych.
Co prawda część jego poglądów zamieściłem w jednej z prezentacji, ale teraz znalazłem ciekawe i godne polecenia kompendium dla młodych adeptów OO: Everything You Know is Wrong.

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

Myśli warte uwagi:

  • obiekty to nie dane+procedury zgromadzone w klasach aby nie rozsypywały się po pamięci
  • Gettery i settery są złe ponieważ naruszają enkapsulację (o zgrozo często wymienia się je jako definicję enkapsulacji!)
  • nie pytaj o dane, żądaj wykonania
  • dziedziczenie jest oczywiście złe:)
  • ciekawe połączenie modelu i GUI przy pomocy wzorca budowniczego (strona 7)
  • parę błędów projektowych w API Javy;P

I najważniejsze przesłanie Allena: Don’t confuse familiar with “right”.

niedziela, 10 sierpnia 2008

Z pamiętnika zdomenowanego modelarza: #3 Artefakty biznesowe

Po krótkim wstępie do Domain Driven Design i po zdawkowym opisie architektury oraz otrząsnąwszy się z ulgą z dysonansu czas przyjrzeć się artefaktom z warstwy domenowej.

W kontekście trzech ostatnich dyskusji (głównie pozablogowych) spróbuję zsyntetyzować zagadnienia podstaw OO (kontrowersyjne dziedziczenie, wzorce projektowe) z paradygmatami DDD.

W DDD logikę domenową modelujemy za pomocą kilku rodzajów artefaktów (które Evans w swej książce nazywa Building Blocks). Mamy zatem niejako do wyboru kilka podstawowych figur, którymi możemy zagrać na różne sposoby:

Encje - rozróżnialne (w przeciwieństwie do Value Objects) obiekty. Intuicyjnie jest to coś co mapujemy na tabele (nie koniecznie 1-1 i nie koniecznie wszystkie atrybuty). W DDD ważne jest aby encje miały oprócz atrybutów również odpowiedzialność (metody biznesowe) - w przeciwnym wypadku mamy do czynienia z anemicznym modelem, o którym już pisałem.

Value Objects (VO) - po prostu opisują jakiś rzeczy i nie są rozróżnialne (w przeciwieństwie do encji). Zazwyczaj nie powinny być modyfikowalne - można je współdzielić więc modyfikacja w jednym miejscu popsuła by szyki w innym. O ile encje zazwyczaj odczytujemy z bazy (lub tworzymy po to aby je tam zapisać) to VO zwykle powstają w wyniku działania Servisu.

Servisy - czasem po prostu pewnych odpowiedzialności nie możemy w naturalny przypisać do żadnej encji ani VO. Wówczas potrzebujemy servisu, który się tym zajmie. Zwróćmy uwagę na podejście - Servis jest na równym poziomie (warstwie) z Encją! Nie jest to servis aplikacyjny (leżący ponad warstwą domenową) a servis biznesowy - artefakt tej samej "wagi" co encja.

Agregaty - rzeczywistość nigdy nie jest na tyle prosta by zamodelować ją przy pomocy jednej Encji. Agregaty to nic innego jak drzewa encji i VO. Ideą agregatu jest to, że mamy dostęp do "roota", który jest niejako interfejsem - to zawsze jemu wydajemy rozkazy - natomiast w jego gestii leży ewentualne delegowanie odpowiedzialności do swych składników.
Łańcuszki typu getCzłowiek().getUkładTrawieia.getŻołądek().add(new Kiełbasa(3)) są niedopuszczalne ponieważ łamią paradygmat enkapsulacji (który o zgrozo jest mylony z prywatnymi polami i publicznymi getterami/setterami). Oczywiście w tym przykładzie człowiek powinien mieć jakiś interfejs (w sensie metodę a nie w sensie słówka kluczowego interface) do karmienia go.
Agregaty są jako spójna całość zapisywane i odczytywane. Z agregatem łączy się kolejny artefakt (już raczej techniczny niż projektowy) fabryka - hermetyzująca po prostu złożony proces tworzenia nowego agregatu (tworzenia nowego a nie odczytywanie istniejącego ze źródła danych).

Polityki - last but not least. Po prostu stary dobry u sprawdzony wzorzec strategii. Gdy jakiś proces biznesowy (lub jego etap) jest alternatywny, lub spodziewamy się jego zmiany (jest niestabilny lub nie do końca przeanalizowany) wówczas enkapsulujemy go poza stabilny interfejs. Znowu enkapsulacja trochę bardziej wyrafinowana niż prywatne pole i publiczny getter/setter. Przy okazji (już o tym pisałem, ale nigdy za wiele powtarzać) trzymamy się GRASP.


Podsumowując w kontekście dziedziczenia: Jak widać DDD zachęca raczej do agregacji. Na dziedziczenie możemy sobie pozwolić np w politykach. Same polityki agregujemy we wnętrzu Servisów, Encji czy VO, ale na niższym poziomie - poziomie implementacji możemy śmiało i bez obaw zagrać dziedziczeniem. Polityka jest koherentna, bo z definicji posiada ściśle określoną jedną odpowiedzialność więc nie będzie tu raczej problemów opisywanych ostatnio.

Zatem patrząc ogólniej: dziedziczenie jest przeniesione z poziomu Encji, VO i Servisów na poziom składników - polityk. W wyniku zamiast definiować kilka różnych klas np Klientów (zwykły, vip, lojalny itp) możemy zaprojektować jedną klasę klienta składającą się między innymi z PolitykiRabatowej, która jest interfejsem. A jak to się w przyszłości rozwinie - nie jest to ważne z punktu widzenia klasy Klienta. Po kolejny przykład zapraszam tu.

//=================================
Porównajmy to z klasycznym podejściem. Znajdujemy rzeczowniki, projektujemy bazę, generujemy z niej klasy encji (właściwie to pascalowe rekordy, albo stuct z C). Później już tylko potrzeba paczek procedur (czasowniki) zamkniętych w klasy (po co te klasy? tylko przeszkadzają - nakładanie gaci przez głowę) i gotowe. Aby bylo bardziej biznesowo to rekordy umieszczam w pakiecie modelu albo domain a paczki procedur w pakiecie services. Radośnie programuję w Pascalu w Javie:)

czwartek, 7 sierpnia 2008

Telefonistka dziedziczy po telefonie

"I once attended a Java user group meeting where James Gosling (Java's inventor) was the featured speaker. During the memorable Q&A session, someone asked him: "If you could do Java over again, what would you change?" "I'd leave out classes," he replied. After the laughter died down, he explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible."

"The extends keyword is evil; maybe not at the Charles Manson level, but bad enough that it should be shunned whenever possible."

- tako rzecze Allen Holub - ekstremista obiektowy.


Odpowiedź na poprzedniego posta skłoniła mnie do naskrobania paru słów o dziedziczeniu w ogóle...

Dużo pisze się o tym, że dziedziczenie jest źle rozumiane, nadużywane czy używane w nieodpowiedni sposób. Można zadać pytanie: o co w ogóle chodzi? Przecież pani w szkole mówiła, że to podstawa OO;)

Relacja dziedziczenia pomiędzy klasami jest najsilniejszym rodzajem powiązania (coupling) co w oczywisty sposób prowadzi do problemu "kruchej klasy bazowej". Zmiany w klasie bazowej mają oczywiście drastyczny wpływ na kod systemu, który po niej dziedziczy. Projektowanie każdej takiej klasy bazowej to zestaw decyzji niemal strategicznych, a jej zmiany to i tak zazwyczaj katastrofa.

Poza tym oczywiście klasa dziedzicząca ciągnie za sobą (jak przykładowy smród za gaciami) bagaż klasy bazowej. Jak słusznie zauważył Paweł w OO nie ma pól i metod regresywnych:) W OO Dziedziczymy po przodku wszystko a nie tylko co jest przydatne - nawet pola prywatne... niby ich nie ma a w pamięci są;)

Dziedziczenie jest złym sposobem na "wyciąganie przed nawias" wspólnych elementów. Lepiej wspólne części hermetyzować do osobnych klas i następnie je agregować/komponować.

Osobiście kieruję się Zasadą Podstawienia Liskov: "Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T."
hehe, ten pseudonaukowy-turbo-bełkot można w skrócie ująć tak: używaj dziedziczenia tylko wtedy, gdy będziesz korzystał z polimorfizmu. I to się sprawdza. Od kiedy staram sie trzymać tej zasady wiele problemów z rozbudową i zmianami po prostu już się nie pojawia. Zamiast dziedziczenia zazwyczaj stosuję kompozycję strategii, która jak mi się wydaje jest "metawzorcem" - opiera się na nim duża część innych wzorców - różny jest jedynie kontekst (ale o tym napiszę w przyszłości).

//=========================
"Klasa telefon i klasa telefonistka, która dziedziczy po telefonie" - taką odpowiedź usłyszałem podczas rozmowy rekrutacyjnej od "młodego, zdolnego i dobrze zapowiadającego się kandydata", na pytanie o jakiś nieksiążkowy przykład dziedziczenia.
Nie przeszedł rekrutacji...

poniedziałek, 4 sierpnia 2008

Strategia na wielokrotne dziedziczenie

Wielokrotne dziedziczenie jest be - wszyscy o tym wiemy. Poza oczywistymi problemami logicznymi narusza jedną z głównych zasad OO - utrzymywanie wysokiej kohezji - klasa powinna mieć jak najmniej odpowiedzialności, najlepiej jedną.

Ostatnio zmagałem się z zaprojektowaniem pewnego modelu dostarczonego z fazy analizy. Model analityczny był w sumie dosyć prosty, jednak kobieca intuicja podpowiadała, że będzie sprawiał problemy z rozbudową w przyszłości. Problem domenowy wyglądał tak: mamy zlecenie podania leku; zakładamy, że w przyszłości na pewno dojdą do wykonania inne rzeczy; możemy również zlecać wykonanie czegoś raz lub wielokrotnie (okresowo, wg zadanego wzorca powtarzania).

Model analityczny wyglądał tak: mamy zlecenie, po którym dziedziczy zleceniePodaniaLeku, po którym to z kolei dziedziczą zlecenia jednorazowegoPodaniaLeku jak i wielokrotnegoPodaniaLeku. Czujecie chyba eksplozję kombinatoryczną, która się szykuje w razie pojawienie się nowych rodzajów rzeczy do zlecenia czy nowych polityk powtarzania, lub (o zgrozo) pojawienie się nowych aspektów zlecenia.

Widać tu jak na dłoni wzorzec projektowy Strategii. Postępując zgodnie z tym wzorcem dojdziemy do modelu, gdzie zlecenie składa się ze strategii powtarzania - lub polityki idąc za terminologią Domain Driven. Zlecenie składa się też ze strategii wykonania pewnej czynności. Zlecenie "widzi" jedynie interfejsy strategii, natomiast z jego punktu widzenia implementacje nie są istotne.
Podejście to zapewni nam również zgodność z kolejną zasadą GRASP.

Wzorzec co prawda "widać" z perspektywy projektowej czy implementacyjnej, jednak z perspektywy analitycznej ponoć wygodniej jest pokazać wielodziedziczenie - po typie powtarzania i po typie czynności do wykonania. Jednak ponieważ wielodziedziczenie jest be, więc aby go uniknąć doszło na poziomie analizy do "rzutowania" aspektu powtarzania na aspekt czynności.

Sytuacja z dystansu wygląda śmiesznie: analitycy "szyfrują" rzeczywistość do jednowymiarowej przestrzeni aby uniknąć wielodziedziczenia, a projektanci muszą później dokonać "odszyfrowania" na swoje strategie;)

Dlatego też wspólnie uznaliśmy, że wielodziedziczenie jest git - ale tylko na poziomie analitycznym:P Dzięki niemu projektanci mogą niemal w sposób automatyczny dokonać transformacji modelu analitycznego na implementacyjny model obiektowy oparty na strategiach. Niemal automatyczny ponieważ wszystkie wielowymiarowe aspekty dokładnie "widać" gdy nie są spłaszczone do jednego wymiaru


//===========================
Tak właściwie to nie jestem do końca przekonany czy aby na pewno wielodziedziczenie jest immanentną cechą obiektów ze świata rzeczywistego. Być może jest to jedynie kwestia wyuczonego postrzegania; wynik bezkrytycznego powielania schematów myślowych. Pamiętam, że sam zanim przestawiłem się na agregację/kompozycję, widziałem wszędzie dziedziczenie - wszak kto ma w ręku młotek, ten wszędzie widzi gwoździe.

GRASP jest doskonale opisany w "Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development" autorstwa Craiga Larmana