You are on page 1of 8

Techniki

Dekorator: wzorzec projektowy na kad bolczk


Pawe Kozowski

Stopie trudnoci: lll

Nazwa wzorca projektowego dekorator jest nieco mylca, poniewa sugeruje, e bdziemy co wzbogaca, dekorowa czy upiksza. Nic bardziej bdnego! Omawiany wzorzec znajduje szerokie zastosowanie, niezalenie od tego, czy projektujemy warstw dostpu do bazy danych, logik biznesow lub kontroler MVC.

J
W SIECI
http://flexi.sourceforge.net/ budowany przez nas framework http://www.picocontainer.org/ Ports Pico http://www.martinfowler.com/ articles/injection.html ciekawe artykuy http://www.phppatterns.com/ ciekawe artykuy

ednym z najczciej omawianych i uywanych wzorcw projektowych jest dekorator. Z punktu widzenia programisty ma same zalety: jest bardzo uyteczny, nieskomplikowany w implementacji i atwy do przyswojenia. Obok singletona jest to prawdopodobnie jeden z pierwszych wzorcw, z ktrymi stykamy si, gdy zaczynamy studiowa przykady i literatur powicon zasadom projektowania. O ile jednak singleton jest obwiniany (czsto susznie!) o promowanie fatalnych rozwiza, to dokadne poznanie dekoratora przyczynio si do powstania wielu ciekawych rozwiza architektonicznych. Nazwa wzorca projektowego "dekorator" jest nieco mylca, poniewa sugeruje, e bdziemy co wzbogaca, dekorowa czy upiksza. Std moe powsta bdne przewiadczenie, e mamy do czynienia z tworem przydatnym jedynie w warstwie prezentacji. Nic bardziej bdnego! Omawiany wzorzec znaj-

duje szerokie zastosowanie, niezalenie od tego, czy projektujemy warstw dostpu do bazy danych, logik biznesow lub kontroler MVC. Jego istot jest moliwo wzbogacenia funkcjonalnoci obiektw danych klas w sposb dynamiczny, bez koniecznoci modyfikowania oryginalnych obiektw. Definicje jak zwykle brzmi zbyt sucho, spjrzmy wic na Listing 1, gdzie

Co naley wiedzie...

Przydatna bdzie znajomo dwch poprzednich artykuw z naszej serii Wzorce projektowe. Czytelnik powinien mie wiedz z obiektowych technik projektowania aplikacji.

Co obiecujemy...

Z artykuu dowiesz si, kiedy i dlaczego warto stosowa wzorzec projektowy dekorator. Na przykadach pokaemy, jak stosujc dekorator, bardzo atwo i w elegancki sposb doda now funkcjonalno do aplikacji bez zbdnej modyfikacji kodu.

www.phpsolmag.org

PHP Solutions Nr 4/2006

Wzorce projektowe: dekorator

Techniki

znajduje si elementarny przykad uycia dekoratora. W przedstawionym przypadku uylimy dekoratora do wzbogacenia istniejcej funkcjonalnoci. Nie musimy jednak zbyt cile sugerowa si nazw wzorca projektowego i spokojnie moemy pozwoli sobie na cakowit zmian funkcjonalnoci dekorowanego obiektu. Dwa proste przykady, przedstawione na wspomnianych listingach, obrazuj praktycznie wszystkie istotne cechy wzorca, z ktrym si zaznajamiamy. Szczeglne istotny do odnotowania jest jeden fakt: dla klienta korzystajcego z udekorowanego (czsto mwimy rwnie - "opakowanego") obiektu obecno dekoratora jest zupenie przezroczysta! Klient nie jest w stanie stwierdzi, czy korzysta z usug oryginalnego obiektu, czy te z jego udekorowanej wersji. Jest to moliwe dziki zachowaniu przez dekorator interfejsu oryginalnego obiektu. Jeli chcielibymy zapamita jak jedn zasad czy zdanie opisujce dekoratory, mogoby to by: Dokorator zachowuje interfejs oryginalnego obiektu i najczciej przyjmuje dekorowany obiekt jako parametr w swoim konstruktorze. Kolejn bardzo mi cech dekoratora jest moliwo uycia wicej ni jednego "opakowania" dla podstawowego obiektu. Oznacza to, i moemy skada now funkcjonalno z wielu ju istniejcych elementw, bez koniecznoci ingerencji w raz stworzony kod. Jest to dokadnie ta sama filozofia, jak spotykamy przy pracy z powok w systemach uniksowych. Doskonale wiemy, jak wiele poytecznych operacji mona wykona, zaprzgajc do wsplnej pracy elementarne narzdzia. To samo odnosi si do dekoratorw: sprytnie czc wiele prostych dodatkw moemy uzyska funkcjonalno, o ktrej nie nio si nawet twrcom piszcym klasy czy te dekoratory (Listing 2). Zanim przejdziemy do omwienia bardziej skomplikowanych przykadw, zatrzymajmy si jeszcze na chwil nad wasnoci dekoratorw, dziki ktrej klient nie jest w stanie rozrni, czy ma do czynienia z obiektem podstawowym, czy te z opakowan jego wersj. Uwany Czytelnik zauway, e ta nierozrnialno jest moliwa, poniewa pomidzy dekoratorem i obiektem dekorowanym zachodzi taka sama relacja ("jest",

ang. IS-A), jak przy dziedziczeniu. Istotnie, przykad z Listingu 2 mona by zapisa tylko i wycznie przy pomocy tworzenia nowych podklas (Listing 3). Jaki jest wic sens wprowadzania zupenie nowej konstrukcji programistycznej i opisywania jej jako wzorca projektowego? Przecie to samo mona uzyska przy uyciu podstawowych konstrukcji programowania obiektowego. Jest jedna, dosy istotna rnica pomidzy dwoma wspomnianymi podejciami. Aby j atwo pokaza, przemodelujmy przykad z Listingu 2, zmieniajc kolejno zastosowanych dekoratorw (Listing 4). Prosz bardzo: caa operacja bya wyjtkowo prosta, odbya si bez koniecznoci modyfikowania istniejcego kodu i dopro-

wadzia do powstania nowej funkcjonalnoci. Niestety, w przypadku dziedziczenia mamy duo trudniejsze zadanie: nie obdzie si bez dosy istotnego przemeblowania napisanego kodu. Powodem jest fakt, i formujc hierarchi dziedziczenia tworzymy do sztywne, statyczne powizanie pomidzy poszczeglnymi klasami. W przypadku dekoratorw nie mamy takiego ograniczenia: moemy dowolnie przestawia kolejno, dodawa nowe elementy do acucha i usuwa ju istniejce. Takie zamiany s moliwe nie tylko w momencie ustalania struktury kodu, ale w dowolnym momencie, nawet w czasie wykonywania skryptu. Dziki dekoratorom mona uzyska wszystkie dobrodziej-

Listing 1. Prosty przykad uycia dekoratora


<?php /** *interfejs, ktory moe by implementowany przez wiele klas *poniewa dekoratory rwnie implementuj ten interfejs *klient korzystajcy z udekorawanej klasy nie jest w stanie *rozrni, czy ma do czynienia z pierwotn, czy udekorowan wersj */ interface UserDAO { public function save(User $user); public function findByLoginNamePassword($loginName, $password); } class UserDAOImpl implements UserDAO { public function save(User $user) { // standardowa funkcjonalno zapisywania // danych uytkownikw do DB } public function findByLoginNamePassword($loginName, $password){ // standardowa funkcjonalno odnajdywania // danych uytkownikw po nazwie } } class UserDAOUpperCaseLoginDecorator implements UserDAO { private $_decoratedDao; public function __construct(UserDAO $decoratedDao){ $this->_decoratedDao = $decoratedDao; } public function save(User $user) { //zadaniem dekoratora jest zapisywanie loginw //tylko wielkimi literami $user->setLoginName(strtoupper($user->getLoginName())); //wywoanie oryginalnej funkcjonalnoci $this->_decoratedDao->save($user); } public function findByLoginNamePassword($loginName, $password) { //implementacja wyszukiwania pozostaje bez zmian return $this->_decoratedDao->findByLoginNamePassword( $loginName, $password); } } //konstruowanie udekorowanej wersji obiektu $dao = new UserDAOUpperCaseLoginDecorator(new UserDAOImpl()); ?>

PHP Solutions Nr 4/2006

www.phpsolmag.org

Techniki

Wzorce projektowe: dekorator


noci w rnych wersjach tej samej, podstawowej aplikacji. Ta mnogo i wygoda zastosowa powoduje, e w zoonej aplikacji moemy mie wiele dekoratorw dla jednego obiektu. Pisanie takiej iloci dekoratorw i zarzdzanie ich konfiguracj stanowi wyzwanie samo w sobie. W dalszej czci artykuu zastanowimy si wic, jak szybko pisa i konfigurowa dekoratory. Zatrzymajmy si na duej nad przyListing 2. czymy dekoratory
<?php class UserDAOPasswordHashingDecorator implements UserDAO { private $_decoratedDao; public function __construct(UserDAO $decoratedDao){ $this->_decoratedDao = $decoratedDao; } public function save(User $user) { //zadaniem dekoratora jest zapisywanie loginw //tylko wielkimi literami $user->setPassword(md5($user->getPassword())); //wywoanie oryginalnej funkcjonalnoci $this->_decoratedDao->save($user); } public function findByLoginNamePassword($loginName, $password) { //przy wyszukiwaniu rwnie szyfrujemy hasa return $this->_decoratedDao->findByLoginNamePassword( $loginName, md5($password)); } } //konstruowanie wielokrotnie udekorowanej wersji obiektu $dao = new UserDAOUpperCaseLoginDecorator(new UserDAOPasswordHashingDecorator( new UserDAOImpl())); ?>

stwa dziedziczenia, ale w sposb bardziej elastyczny. Nie oznacza to oczywicie, i namawiamy Was, by od dzisiaj nie uywa dziedziczenia i pisa tylko dekoratory. Jak kady wzorzec projektowy, dekorator nie powinien by stosowany tylko w celu otrzymania bardziej elastycznej architektury, ale w przypadkach, w ktrych bdzie naprawd pomocny. Uzbrojeni w solidne podstawy teoretyczne moemy przyjrze si wreszcie przykadom pokazujcym, jak w atwy i efektywny sposb rozbudowa nasze aplikacje bez koniecznoci modyfikacji ich podstawowego kodu. Wrmy zatem do przykadw. Jedn z dobrych praktyk architektonicznych jest dzielenie kodu aplikacji na warstwy. W typowej aplikacji WWW, najnisz warstw stanowi baza danych (najczciej w postaci relacyjnej bazy danych lub zwykych plikw). Powyej umieszczamy wartw dostpu do danych w postaci obiektw DAO (ang. Data Access Objects). Ju ta jedna wspomniana warstwa jest okazj do zaimplementowaniu wielu funkcjonalnoci w postaci dekoratorw. Wyobramy sobie, e chcemy zapisywa czas wyszukiwania danych w DB. Nic prostszego: zamiast modyfikowa istniejcy kod, otoczmy go dodatkow klas (Listing 5). A moe mamy DAO suce do zapisu danych uytkownikw i w pewnych przypadkach chcemy szyfrowa hasa. Prosz bardzo: zamiast utrzymywa dwie zblione wersje tego samego DAO, duo efektywniej rozwiemy problem stosujc prosty dekorator bez modyfikowania i duplikowania istniejcego kodu. Przykady mona mnoy rwnie w innych warstwach sytemu. Jeli pomylimy o dowolnej logice biznesowej zapisujcej dane, to dekoratory znajd zastosowanie przy wersjonowaniu danych, zapisywaniu logw zmian, kontroli dostpu itd. Przesuwajc si jeszcze jedn warstw wyej, do kontrolerw MVC, szybko odkryjemy kolejne zastosowania: ledzenie zachowa uytkownikw (np. cieki poruszania si po serwisie WWW) czy sprawdzanie uprawnie. Zastosowania i przykady mona by mnoy w nieskoczono. Poprzestamy wic na stwierdzeniu, e wzorzec dekoratora jest niezwykle uyteczny i podpowiada eleganckie rozwizania dla czsto spotykanych problemw zwizanych z dodawaniem specyficznej funkcjonal-

woanym przykadem zastosowania dekoratorw do dodania obsugi uprawnie na poziomie warstwy kontrolerw MVC. Listing 6 pokazuje dwa przykadowe kontrolery oraz opakowanie z dekoratorw sprawdzajcych uprawnienia. Przedstawiony schemat dodawania obsugi uprawnie do aplikacji jest niezwykle efektywny. Po pierwsze, pozwala uruchamia t sam aplikacj ze sprawdzaniem uprawnie i bez, w zalenoci od wymaga klienta.

Listing 3. Przykad z Listingu 2 zapisany przy pomocy nowych podklas


<?php class UserDAOUpperCaseLogin extends UserDAOImpl { public function save(User $user) { $user->setLoginName(strtoupper($user->getLoginName())); parent::save($user); } } class UserDAOPasswordHashing extends UserDAOUpperCaseLogin { public function save(User $user) { $user->setPassword(md5($user->getPassword())); parent::save($user); } public function findByLoginNamePassword($loginName, $password) { return parent::findByLoginNamePassword($loginName, md5($password)); } } //konstruowanie wersji obiektu z dziedziczeniem $dao = new UserDAOPasswordHashing(); ?>

Listing 4. Zmieniona kolejno zastosowanych dekoratorw w stosunku do Listingu 2. Tworzymy now funkcjonalno bez modyfikacji istniejcego kodu.
<?php $dao = new UserDAOPasswordHashingDecorator(new UserDAOUpperCaseLoginDecorator( new UserDAOImpl())); ?>

www.phpsolmag.org

PHP Solutions Nr 4/2006

Wzorce projektowe: dekorator

Techniki

Rysunek 1. Uproszczony graf obiektw w naszej aplikacji Z drugiej strony, taka architektura umoliwia niezalene pisania kodu biznesowego i odpowiedzialnego za bezpieczestwo. Wreszcie, moemy sobie wyobrazi zastosowanie rnych silnikw do sprawdzania uprawnie (np. wasny lub GACL). Niestety, nasze wszystkie zachwyty troch przybledn, jeli uwiadomimy sobie, e dla kadego kontrolera trzeba stworzy osobny dekorator. Nie jest to moe wielkim problemem przy piciu czy dziesiciu kontrolerach, ale staje si koszmarem przy duych aplikacjach, gdzie czsto spotkamy setki klas typu kontroler. Nasze miny stan si jeszcze bardziej nietgie, jeli uwiadomimy sobie, e kady z dekoratorw bdzie do siebie bardzo podobny. Ponowny rzut oka na Listing 6 faktycznie ujawnia, e w kolejnych dekoratorach rni si tylko nazwy klas i metod zasadnicza logika pozostaje praktycznie bez zmian. Bezmylne pisanie niemal identycznych dekoratorw nie jest zajciem szczeglnie ciekawym czy produktywnym, a dodatkowo to oczywista duplikacja kodu. Musimy wic znale jaki sposb na zautomatyzowanie mudnego zajcia. PHP5, jako jzyk niezwykle dynamiczny daje nam szerokie moliwoci generowania szablonowego kodu w locie. Spord wielu opcji dwie wydaj si najciekawsze: uycie magicznej metody __call lub generowanie caego kodu dekoratora w czasie dziaania skryptu. Obie metody maj swoje wady i zalety, ktre za chwil omwimy, ale najpierw spjrzmy na Listing 7, gdzie znajduj si przykadowe dekoratory (z Listingu 6), zaimplementowane przy uyciu obu wspomnianych metod. Dekorator stworzony przy pomocy __call jest dosy prosty i pozwala prezycyjnie regulowa, ktre metody w dekoratorze s generowane automatyczne, a ktre rcznie. Po prostu dla tych, ktre chcemy napisa sami, tworzymy konkretn metod, a wprzypadku pozostaych metod polegamy na __call. Omawiana wanie metoda ma w zasadzie tylko jedn wad w ten sposb wygenerowane dekoratory nie mog by uyte w wywoaniach funkcji i metod, dla ktrych okrelono typ argumentu (a wic dla konstrukcji $obiekt->nazwaMetody(TypArgumentu $agrument);). Jest to do powana wada, poniewa na pocztku podkrelalimy, i jedn z cech dekoratora jest zachowanie interfejsu oryginalnego obiektu. Zastosowanie __call do generowania dekoratorw wyklucza silniejsz kontrol typw, a tym samym interfejsw implementowanych przez podstawow i udekorowan klas.

Aby obej zidentyfikowany problem, posuymy si prost koncepcyjnie metod generowaniem kodu w locie. Spjrzmy na Listing 8, gdzie pokazujemy uycie specjalnie przygotowanej biblioteki (PHPProxy, dostpnej na sourceforge.net) do generowania kodu. Jak wida, samo jej uycie jest niezwykle proste sprowadza si do zaimplementowania tylko tej logiki, ktra ma si znale w dekoratorze nie musimy powiela ani jednego znaku kodu. Tak prosta implementacja jest moliwa dziki istnieniu interfejsu ProxyInvocationHandler. W interfejsie tym zdefiniowana jest tylko jedna metoda invoke($method, $args). Nie trzeba by potomkiem Sherlocka Holmesa by wydedukowa, e metoda ta jest uruchamiana w momencie wywoywania metod na dekoratorze, a w argumetach otrzymujemy wszystkie niezbdne informacje o wywoaniu tj. nazw metody i jej argumenty. Korzystajc z implementacji interfejsu ProxyInvocationHandler z opisywanej biblioteki (Listing 9), moemy atwo zdecydowa, czy chcemy tylko udekorowa oryginaln funkcjonalno (a wic wywoa j podczas uruchamiania dekoratora), czy te stworzy zupenie nowy kod: cay sekret tkwi w meto-

Listing 5. Dodajemy now funkcjonalno do zapisywania czasu wyszukiwania danych w DB


<?php class PerformanceLoggingUserDAO implements UserDAO { private $_decoratedDao; private $_startTime; public function __construct(UserDAO $decoratedDao){ $this->_decoratedDao = $decoratedDao; } public function save(User $user) { $this->startMethodExecution(); $this->_decoratedDao->save($user); $this->stopMethodExecution('save'); } public function findByLoginNamePassword($loginName, $password) { $this->startMethodExecution(); return $this->_decoratedDao->findByLoginNamePassword( $loginName, $password); $this->stopMethodExecution('findByLoginNamePassword'); } private function startMethodExecution(){ $this->_startTime = microtime(); } private function stopMethodExecution($methodName){ $executionTime = microtime() - $this->_startTime; echo "Wykonanie metody '$methodName' zajo $executionTime <br>"; } } //konstruowanie udekorowanej wersji obiektu $dao = new PerformanceLoggingUserDAO(new UserDAOImpl()); ?>

PHP Solutions Nr 4/2006

www.phpsolmag.org

Techniki

Wzorce projektowe: dekorator

dzie getDelegate(), dzieki ktrej moemy pobra dekorowany obiekt i wywoa na nim dowoln metod. Uycie biblioteki PHPProxy pozwala generowa dekoratory przy minimalnym nakadzie pracy ze strony programisty i przy eliminacji powtrze w kodzie. Niestety, nie jest to metoda pozbawiona wad. Po pierwsze, generowanie kodu w czasie dziaania programu moe by zbyt czasochonne w przypadku mocno obcionych serwisw internetowych, gdzie wydajno jest rzecz krytyczn. Drugi, troch mniej uciliwy problem, zwizany jest z przypadoci nkajca wszystkie rozwizania oparte o generowanie kodu. Ot podczas wykonania programu uruchamiany jest rwnie kod, ktry... nie jest nigdzie zapisany na stae. Oznacza to, e w pewnych warunkach ledzenie wykonania programu (np. podczas wyszukiwania bdw) moe by nieco utrudnione. Tak czy inaczej, mimo przedstawionych wad, jest to zdecydowanie najatwiejsza metoda tworzenia wielu dekoratorw. Podsumujmy nasz dotychczasow wiedz o dekoratorach. Na wstpie stwierdzilimy, e jest to bardzo poyteczny wzorzec, ktry moe by stosowany praktycznie w kadej warstwie systemu. Jest to alternatywa do dziedziczenia obiektw, umoliwiajca wzbogacanie lub zmian istniejcej funkcjonalnoci, bez koniecznoci modyfikacji raz napisanego kodu. Dekorator pada jednak troch ofiar wasnego sukcesu mnogo jego zastosowa powoduje, e w bardziej rozbudowanej aplikacji moemy wykorzysta setki obiektw implementujcych wzorzec dekoratora. Aby nie zgin w tym gszczu podobnych obiektw, znalelimy dwie metody na dynamiczne generowanie dekoratorw. Niestety, to nie koniec problemw, ktre musimy rozwiza, aby efektywnie wykorzysta dekoratory jako integraln cz rozwizania architektonicznego. Zamy, e Rysunek 1 przedstawia uproszczony graf obiektw w naszej aplikacji. Obiekty te s oczywicie uoone w warstwy, natomiast obiekty s poczone midzy sob sieci zalenoci. Ju samo poprawne skonstruowanie takiej sieci obiektw moe by nie lada wyzwaniem, o czym Czytelnik mg si przekona, ledzc mj artyku Obiektowa linia montaowa, czyli przejrzyste

Rysunek 2. Dodajc dekoratory, wprowadzamy do omawianego grafu kolejny stopie skomplikowania, opakowujc wybrane obiekty aplikacji j Listing 6a. Dwa przykadowe kontrolery oraz opakowanie z dekoratorw sprawdzajcych uprawnienia
<?php class useraction { private $_userService; public function __construct(UserService $userService) { $this->_userService = $userService; } public function listall(HttpRequest $request, ModelAndView $mv){ $users = $this->_userService->findAll(); $mv->addToModel('users',$users); $mv->setView('userslist'); return $mv; } public function listwithexpensivequery( HttpRequest $request, ModelAndView $mv){ $users = $this->_userService->findUserByExpensiveQuery(); $mv->addToModel('users',$users); $mv->setView('userslist'); return $mv; } public function addform(HttpRequest $request, ModelAndView $mv){ $mv->setView('useraddform'); return $mv; } public function add(HttpRequest $request, ModelAndView $mv){ $user = new User( $request->getParam('login'), $request->getParam('pass'), $request->getParam('firstname'), $request->getParam('lastname')); $mv->addToModel('user',$user); try { $this->_userService->addUser($user); $mv->setView('useraddconfirm'); } catch (UserExistsException $e) { $mv->addToModel('adduser_error','User exists!'); $mv->setView('useraddform'); } return $mv; } } class homepage { public function show(HttpRequest $request, ModelAndView $mv){ $mv->setView('homepage'); return $mv; } }

www.phpsolmag.org

PHP Solutions Nr 4/2006

Wzorce projektowe: dekorator

Techniki

Rysunek 3. W Pico zawarta jest caa konfiguracja zwizana z poczeniami pomidzy obiektami. Aby wprowadzi dodatkowy obiekt poredniczcy, musimy jedynie przekonfigurowa poczenia. Listing 6b. Dwa przykadowe kontrolery oraz opakowanie z dekoratorw sprawdzajcych
// Klasa pomocnicza dla dekoratorw, gdzie odbywa si // waciwe sprawdzanie uprawnie abstract class AbstractActionSecurityDecoratorImpl { private $_decoratedAction; public function __construct($decoratedAction) { $this->_decoratedAction = $decoratedAction; } public function executeTargetActionWithSecurityCheck( $targetAction, HttpRequest $request, ModelAndView $mv){ //tutaj tylko proste sprawdzanie, czy zalogowany, //ale rwnie atwo zintegrowa np. GACL session_start(); if ($_SESSION['user'] == null) { $mv->setView('loginform'); return $mv; } else { return $this->_decoratedAction->$targetAction($request, $mv); }

} } //Mimo, i obie akcje s zupenie inne, //to dekoratory s niemal identyczne! //Oczywista duplikacja kodu! class UserActionSecurityDecoratorImpl extends AbstractActionSecurityDecoratorImpl { public function listall(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck( 'listall', $request, $mv); } public function listwithexpensivequery( HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck( 'listwithexpensivequery', $request, $mv); } public function addform(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck( 'addform', $request, $mv); } public function add(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck('add', $request, $mv); } } class HomepageActionSecurityDecoratorImpl extends AbstractActionSecurityDecoratorImpl { public function show(HttpRequest $request, ModelAndView $mv){ return $this->executeTargetActionWithSecurityCheck('show', $request, $mv); } } ?>

i elastyczne aplikacje w PHP5, z numeru 1/2006. Dodajc dekoratory, wprowadzamy do omawianego grafu kolejny stopie skomplikowania, opakowujc wybrane obiekty aplikacji (Rysunek 2). Warto przy tym zauway, e udekorowaniu bd podlegay gwnie obiekty infrastrukturalne (kontrolery, DAO itd.), a nie domenowe. Musimy wic znale jaki atwy sposb na dodanie wielu dekoratorw w caej aplikacji, bez koniecznoci rcznego przebudowywania pocze. Przy skadaniu skomplikowanych grafw obiektw doskonale sprawdza si wzorzec architektoniczny IoC (ang. Inversion of Control), o ktrym rwnie pisalimy w wyej wspomnianym artykule. Mamy szczcie, poniewa ten sam wzorzec rwnie znakomicie uatwia dodawanie dekoratorw. Dlaczego? Przypomnijmy, e w przypadku stosowania wzorca IoC bardzo pomocne s biblioteki typu kontener IoC. Te lekkie kontenery bior na siebie cay ciar tworzenia skomplikowanych grafw obiektw, dbajc przy tym o waciwe rozwizanie zalenoci pomidzy obiektami. Kontener IoC jest wic jednym, centralnym miejscem, gdzie powoywane s do ycia instancje obiektw infrastrukturalnych. Doskonale, o to nam wanie chodzio. To scentralizowane miejsce tworzenia obiektw pozwala nam atwo doda nasze "opakowania". Spjrzmy na Listing 10, gdzie znajdziemy przykad wykorzystania Pico dla PHP lekkiego kontenera IoC do dodania dekoratorw do obiektw aplikacji. Jak wida, caa operacja jest stosunkowo prosta wystarcz dwie linijki kodu. Przeanalizujmy dokadniej przykad z Listingu 10. Widzimy na nim pocztkowo dwa wsppracujce ze sob obiekty: serwisowy i DAO. S to obiekty infrastrukturalne, podczas dzialania aplikacji potrzebny jest zwykle tylko jeden egzemplarz takiego obiektu. W tym przypadku to Pico dba o powoanie do ycia instancji obiektw oraz ich poczenie. Aby zastosowa dekorator zliczajcy czas wykonania poszczeglnych metod, musimy w jaki sposb rozerwa cise poczenie pomidzy obiektem DAO i obiektem z DAO korzystajcym. Przy klasycznym podejciu do budowy programw, gdybymy uyli operatora new, metod statycznych lub fabryk, mielibymy mae szanse na wprowadzenie trzeciego obiektu w acuch powi-

PHP Solutions Nr 4/2006

www.phpsolmag.org

Techniki

Wzorce projektowe: dekorator


za. W przypadku kontenera IoC sprawa jest o wiele prostsza, poniewa w Pico zawarta jest caa konfiguracja zwizana z poczeniami pomidzy obiektami. Jedyne, co musimy zrobi, to przekonfigurowa te poczenia w taki sposb, by wprowadzi dodatkowy obiekt poredniczcy (Rysunek 3). W Pico robimy to przy pomocy parametrw komponentu, tak jak pokazalimy w drugiej czci Listingu 10. Oczywicie pokazany sposb mona wykorzysta do uycia wicej ni jednego dekoratora (Listing 11). Kontenery IoC w znaczcy sposb uatwiaj stosowanie dekoratorw w zoonych aplikacjach. Dzieki nim moemy w jednym, centralnym miejscu, skonfigurowa poczenia midzy obiektami i tym samym doda nowy element do takiego poczenia. Sposb jest do prosty i nie wymaga zmian w kodzie PHP jedynie w konfiguracji kontenera IoC. Listing 9. Korzystajc z implementacji interfejsu ProxyInvocationHandler z opisywanej biblioteki moemy atwo zdecydowa, czy chcemy tylko udekorowa oryginaln funkcjonalno, czy te stworzy zupenie nowy kod
<?php interface ProxyInvocationHandler { public function invoke($method, $args); } class DelegatingInvocationHandler implements ProxyInvocationHandler { private $_delegate = null; public function __construct( $delegate){ $this->_delegate = $delegate; } public function invoke( $method, $args){ $reflectionClass = new ReflectionClass( get_class($this-> getDelegate())); $reflectionMethod = $reflectionClass-> getMethod($method); return $reflectionMethod-> invokeArgs( $this->getDelegate(),$args); } public function getDelegate(){ return $this->_delegate; } } ?>

Listing 7. Dekorator (z Listingu 6), zaimplementowany przy uyciu __call oraz generowanie caego kodu dekoratora w czasie dziaania skryptu
<?php // implementacja dekoratora z wykorzystaniem metody __call class UserActionSecurityDecoratorCallImpl extends AbstractActionSecurityDecoratorImpl { public function __call ($methodName, $args ) { $request = $args[0]; $mv = $args[1]; return $this->executeTargetActionWithSecurityCheck( $methodName, $request, $mv); }} class HomepageActionSecurityDecoratorCallImpl extends AbstractActionSecurityDecoratorImpl { public function __call ($methodName, $args ) { $request = $args[0]; $mv = $args[1]; return $this->executeTargetActionWithSecurityCheck( $methodName, $request, $mv); }} ?>

Listing 8. Dziaanie PHPProxy do dynamicznego generowania kodu


<?php // biblioteka do dynamicznego generowania klas require_once(dirname(__FILE__).'/phpproxy/src/proxygenerator.inc.php'); // wprowadzamy interfejs na fragment kodu sprawdzajcy uprawnienia, aby mie // moliwo stosowania rnych sposobw i bibliotek interface SecurityChecker { function hasRightsFor(User $user, $actionToCheckRightsFor); } class SecurityCheckerIsNotNull implements SecurityChecker { function hasRightsFor(User $user, $actionToCheckRightsFor){ if ($user != null) { //tu tylko proste sprawdzanie, ale chcemy pokaza, //e kod odpowiedzialny za sprawdzanie uprawnie //jest wydzielony i moe by uyty w dekoratorach return true; } else { return false; } } } // Cz wsplna dla wszystkich dekoratorw. Nie ma tu duplikacji kodu jak na // Listingu 7.Caa logika zwizana ze sprawdzeniem uprawnie i wyowoaniem // (lub nie) dekoratora zamknita jest w tej jednej klasie class SecurityCheckerMethodInvImpl extends DelegatingInvocationHandler { private $_securityChecker; private $_decoratedAction; public function __construct(SecurityChecker $securityChecker) { $this->_securityChecker = $securityChecker; } public function invoke($method, $args) { $request = $args[0]; $mv = $args[1]; $user = getLoggedInUser(); if ($this->_securityChecker($user, $method)){ return parent::invoke($method, $args); } else { $mv->setView('loginform'); return $mv; } } public function getLoggedInUser(){ session_start(); return $_SESSION['user']; } } $proxyGenerator = new ProxyClassGenerator(); $secureMethodInvocation = new SecurityCheckerMethodInvImpl( new SecurityCheckerIsNotNull()); // dynamiczne wygenerowanie dowolnego dekoratora sprowadza si do jednej linii // kodu! $useractionDecorated = $proxyGenerator->getProxy( 'useraction', $secureMethodInvocation); $homepageDecorated = $proxyGenerator->getProxy( 'homepage', $secureMethodInvocation); ?>

www.phpsolmag.org

PHP Solutions Nr 4/2006

Wzorce projektowe: dekorator


Rozwizanie jest prawie idealne. Prawie, bo w dalszym cigu dla kadego dekoratora trzeba doda nowy wpis w konfiguracji i zmodyfikowa poczenia midzy obiektami. Dla twrczo lewniwego programisty to zdecydowanie zbyt duy wysiek. Jeli pomylimy caociowo o stylu programowania, w ktrym napisany zosta Listing 10, to powinna nasun si

Techniki

Listing 10. Przykad wykorzystania Pico dla PHP lekkiego kontenera IoC do dodania dekoratorw do obiektw aplikacji
<?php $parentPico = new DefaultPicoContainer(); $parentPico->regComponentImpl('FrontController', 'FrontControllerImpl'); $parentPico->regComponentImpl( 'ActionResolvingStrategy', //componentKey 'PicoActionResolvingStrategy', //componentClass array ('paramName' => 'action') ); $parentPico->regComponentImplWithIncFileName( dirname(__FILE__).'/lib/SmartyViewResolver.php', 'ViewResolvingStrategy', 'SmartyViewResolvingStrategy', array ( array('compile_dir' => dirname(__FILE__).'/work/templates_c'), 'file:'.dirname(__FILE__).'/templates/smarty/', '.tpl') ); $pico = new DefaultPicoContainer(null, $parentPico); $pico->regComponentInstance($pico, 'PicoContainer'); $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/../shared/model/model.inc.php','UserService','UserServiceImpl'); $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/../shared/model/model.inc.php','UserDAO','UserDAOPDOImpl'); $pico->regComponentImpl('PDO','PDOPicoAdapter',array ( 'pgsql:dbname=ditalkdb;host=localhost', 'postgres', 'postgres')); //actions $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/loginaction_action.php','loginaction','loginaction'); $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/useraction_action.php','useractionTarget', 'useraction'); $pico->regComponentImpl('useraction', 'UserActionSecurityDecoratorImpl', array ('decoratedAction' => new BasicComponentParameter('useractionTarget'))); $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/homepage_action.php','homepageTarget','homepage'); $pico->regComponentImpl('homepage', 'HomepageActionSecurityDecoratorImpl', array ('decoratedAction' => new BasicComponentParameter('homepageTarget'))); $fc = $pico->getComponentInstance('FrontController'); $fc->doService(new HttpRequest()); ?>

nam pewna refleksja. Ot w opisywanym przypadku stosujemy dekoratory do globalnych zmian w caej aplikacji. Patrzymy na dziaajce skrypty i mylimy: teraz chcielibymy doda logowanie do wszystkich obiektw DAO, eby zobaczy, gdzie jest wskie gardo wydajnociowe, albo: teraz udekorujmy wszystkie kontrolery, eby sprawdza uprawnienia. Niestety, wypracowana do tej pory metoda skazuje nas na mudne deklarowanie pojedynczych dekoratorw. Gdyby tylko istniaa konstrukcja programistyczna, pozwalajca atwo wyrazi dania typu: obiekty okrelonego typu udekoruj danym kodem, moglibymy dosownie w kilku linijkach kodu wprowadzi do aplikacji logowanie czy sprawdzanie uprawnie. Zupenie niezalenie od iloci obiektw do udekorowania. Na pocieszenie chcemy powiedzie, e takie metody ju powstaj (przykadowy Listing 11)! Jest to idea bardzo zbliona do programowania aspektowego (ang. Aspect Oriented Programming), ale to ju temat na zupenie inny artyku.

Podsumowanie

Listing 11. Przykad rozwizania, ktre nie skazuje nas na mudne deklarowanie pojedynczych dekoratorw
<?php //rejestracja poprzednich komponentw //jak na Listingu 10 $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/useraction_action.php','useraction', 'useraction'); $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/homepage_action.php','homepage','homepage'); $pico->regComponentImplWithIncFileName(dirname(__FILE__). '/actions/loginaction_action.php','loginaction','loginaction'); //security decorators $pico->regComponentImpl('SecurityMethodInterceptor'); $pico->registerAspect(new Aspect(new RegExpNameMatchingPointcut( '/^[a-km-z]+action$/'),new MatchingAllPointcut(), 'SecurityMethodInterceptor')); ?>

W artykule pokazalimy, e dekorator jest prostym i niezwykle poytecznym wzorcem projektowym. Jest tak uyteczny, e w pewnym momencie moemy mie ochot zastosowa go na bardzo szerok skal. Aby jednak zrobi to efektywnie, musimy zadba o rozwizanie dwch problemw: automatycznego generowania dekoratorw oraz ich konfiguracji w caej aplikacji. Z pierwszym problem doskonale poradzimy sobie dynamicznie generujc powtarzalny kod dekoratora. Zastosowanie kontenera IoC wpynie dodatnio na architektur naszej aplikacji i umoliwi atwe konfigurowanie dekoratorw. Jeli zrozumiemy i opanujemy przedstawione powyej triki, wiat programowania aspektowego nie bdzie mia przed nami tajemnic. n

O autorze:
Pawe Kozowski jest pracownikiem SUPERMEDIA, gdzie od roku 2000 projektuje i tworzy zoone aplikacje WWW w PHP. Obecnie zajmuje si rozwijaniem frameworkw i bibliotek ORM opartych na PHP5. Jest autorem portu PicoContainer dla PHP5 i wielu publikacji powiconych PHP. Kontakt: pkozlowski@phpsolmag.org

PHP Solutions Nr 4/2006

www.phpsolmag.org

You might also like