Autor: Bartosz Maciaszek
Data publikacji: 02.11.2007, 11:12 | Ostatnia modyfikacja: 02.11.2007, 11:12
Pierwszy z serii artykuÅ‚ traktujÄ…cy o wzorcach projektowych w PHP. OmówiÄ™ w nim zasadÄ™ dziaÅ‚ania wzorca projektowego Visitor (Wizytator) i analizujÄ…c przykÅ‚ady postaram siÄ™ zachÄ™cić czytelników do używania go.
Wizytator to wzorzec projektowy, który zapewnia nam prosty sposób na wykonanie jednej lub wiÄ™kszej iloÅ›ci operacji na okreÅ›lonej grupie obiektów. Do zaprojektowania wymaganej zależnoÅ›ci bÄ™dziemy potrzebować dwóch interfejsów okreÅ›lajÄ…cych funkcjonalnoÅ›ci, które bÄ™dzie wymagać implementacja. Te dwa interfejsy oddzielÄ… od siebie dwa najbardziej elementarne grupy obiektów zależnoÅ›ci - obiekt "odwiedzajÄ…cy" (Visitor) i obiekt "odwiedzany" (Visitable). Zanim przejdziemy dalej proponujÄ™ wyobrazić sobie taki obrazek: Jest dom w którym mieszka rodzina skÅ‚adajÄ…ca siÄ™ z dwojga dorosÅ‚ych (matka i ojciec) oraz dziecka. Mamy też czÅ‚owieka spoza domu, powiedzmy listonosza, który codziennie rano odwiedza dom i mieszkaÅ„ców. RolÄ™ "magazynu" odgrywa tu dom, mieszkaÅ„cy bÄ™dÄ… osobami odwiedzanymi, a listonosz wizytatorem, który odwiedzajÄ…c dom i jego mieszkaÅ„ców ma speÅ‚nić swoje okreÅ›lone zadanie. Znamy już podziaÅ‚ ról i zależnoÅ›ci pomiÄ™dzy osobami - obiektami.
Za pomocÄ… kolejnego przykÅ‚adu przejdźmy do programowania. Wyobraźmy sobie teraz nieco innÄ… sytuacjÄ™ w której mamy rower posiadajÄ…cy dwa koÅ‚a i pompkÄ™, za pomocÄ… której chcemy je napompować. Potrzebujemy stworzyć 3 obiekty - "Bicycle", "Wheel" oraz "Pump". Rower (Bicycle) bÄ™dzie "magazynem" kóÅ‚ (Wheel) - obiektów, które mogÄ… zostać odwiedzone przez pompkÄ™ (Pump) - wizytatora. Potrzebujemy również wspomnianych wczeÅ›niej interfejsów - Visitor i Visitable. ZabawÄ™ zacznijmy od interfejsów:
interface Visitor { public function visit(Visitable $visitable); } interface Visitable { public function accept(Visitor $visitor); }
Schemat jest dość prosty - Visitor za pomocÄ… metody "visit" bÄ™dzie odwiedzać obiekt typu Visitable, a on z kolei bÄ™dzie mógÅ‚ zgodzić siÄ™ na jego wizytÄ™ za pomocÄ… metody "accept". Jako, że znamy już zależnoÅ›ci pomiÄ™dzy naszymi trzema obiektami, jesteÅ›my w stanie okreÅ›lić, że obiekty "Bicycle" i "Wheel" bÄ™dÄ… implementować interfejs Visitable - bÄ™dÄ… odwiedzanymi, a "Pump" - Visitor - bÄ™dzie odwiedzać.
Zaprogramujmy więc nasze obiekty. Zacznijmy od najbardziej elementarnych, czyli roweru i koła:
class Bicycle implements Visitable { private $wheels = array(); public function addWheel(Wheel $wheel) { array_push($this->wheels, $wheel); } public function accept(Visitor $visitor) { foreach($this->wheels as $wheel) { $wheel->accept($visitor); } } }
class Wheel implements Visitable { private $name = null; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } public function accept(Visitor $visitor) { $visitor->visit($this); } }
Funkcjonalność tej klasy także nie powinna być trudna do odgadniÄ™cia. Jest metoda "addWheel", która dodaje na stos obiekt koÅ‚a. Poznana wczeÅ›niej metoda 'accept' zmieniÅ‚a nieco zachowanie - za zadanie ma wykonanie metody 'accept' na wszystkich koÅ‚ach ze stosu. DziÄ™ki temu wystarczy, że odwiedzimy obiekt Bicycle. To on zadba o to, aby pompka wszystkie koÅ‚a.
Na koniec najważniejsza klasa, a mianowicie nasz wizytator w którym zawarta bÄ™dzie caÅ‚a funkcjonalność.
class Pump implements Visitor { public function visit(Visitable $visitable) { echo 'Pompuję koło ' . $visitable->getName() . "..\n"; // inne operacje na obiekcie $visitable } }
W klasie tej definiujemy metodÄ™ 'visit', w której zamieszczamy całą logikÄ™ którÄ… ma wykonać obiekt odwiedzajÄ…cy na obiekcie odwiedzanym. Nie musimy siÄ™ przejmować ile kóÅ‚ tak naprawdÄ™ mamy do napompowania - tym zajmuje siÄ™ inna klasa - Bicycle, skupiamy siÄ™ tylko i wyłącznie na tym, co mamy zrobić. DziÄ™ki temu zyskujemy wyraźne odseparowanie logiki wykonawczej od struktury i zależnoÅ›ci pomiÄ™dzy obiektami.
Na koniec stwórzmy obiekty naszych klas i wykonajmy "odwiedzenie". Kod każdej z klas celowo umieÅ›ciÅ‚em w osobnych plikach, aby podkreÅ›lić separacjÄ™ i przejrzystość.
<?php // Dołączamy potrzebne pliki require_once 'Visitor.php'; require_once 'Visitable.php'; require_once 'Pump.php'; require_once 'Bicycle.php'; require_once 'Wheel.php'; // Tworzymy obiekt roweru $bicycle = new Bicycle; // "rejestrujemy" koła $bicycle->addWheel(new Wheel('przednie')); $bicycle->addWheel(new Wheel('tylne')); // Pompujemy wszystkie koła odwiedzając obiekt klasy Bicycle obiektem klasy Pump $bicycle->accept(new Pump); ?>
Wynikiem działania tego kodu będzie:
Pompuję koło przednie.. Pompuję koło tylne..
Nic nie stoi na przeszkodzie abo stworzyć kolejnych "wizytatorów", którzy bÄ™dÄ… wykonywać okreÅ›lone dziaÅ‚ania. Wykonanie kolejnej serii dziaÅ‚aÅ„ sprowadzać siÄ™ bÄ™dzie do dodania jednej linii kodu:
$bicycle->accept(new KolejnaKlasaImplementujacaInterfejsVisitor);
Nie musimy siÄ™ również ograniczać do odwiedzania obiektów jednego typu. Do naszego roweru możemy doÅ‚ożyć kierownicÄ™, dynamo i siodeÅ‚ko, a wizytatorem może zostać mechanik, który bÄ™dzie umiaÅ‚ naprawić dowolnÄ… część roweru. Najlepsze jest to, że nie bÄ™dziemy musieli modyfikować kodu obiektów odwiedzanych, ograniczymy siÄ™ tylko do stworzenia bardziej elastycznego kody wizytatora, przyjrzyjmy siÄ™ takiemu przykÅ‚adowi:
<?php $bicycle = new Bicycle; $bicycle->addElement(new Wheel('przednie')); $bicycle->addElement(new Wheel('tylne')); $bicycle->addElement(new SteeringWheel); $bicycle->addElement(new Saddle); $bicycle->addElement(new Dynamo); $bicycle->accept(new Mechanic); ?>
Metoda 'addElement' w klasie Bicycle może być stworzona na bazie metody 'addWheel' z tÄ… różnicÄ…, że jako argument przyjmować bÄ™dzie mogÅ‚a obiekt dowolnej klasy implementujÄ…cej interfejs Visitable, np.:
public function addElement(Visitable $element) { array_push($this->elements, $element); }
Klasa wizytatora "Mechanic" będzie musiała być nieco inna, jako że ma posiadać więcej umiejętności:
- pompować koło,
- przykręcać siodełko
- wyprostować kierownicę
- naprawić dynamo
Nie mniej jednak interfejs jej nie zmieni się - od "zewnątrz" będzie to najzwyklejszy Visitor:
class Mechanic implements Visitor { public function visit(Visitable $visitable) { $methodName = 'visit' . get_class($visitable); $this->$methodName($visitable); } private function visitWheel(Visitable $visitable) { echo 'pompuję koło ' . $visitable->getName() . '!'; } private function visitSaddle(Visitable $visitable) { echo 'przykręcam siodełko!'; } private function visitSteeringWheel(Visitable $visitable) { echo 'prostuję kierownicę!'; } private function visitDynamo(Visitable $visitable) { echo 'naprawiam dynamo!'; } }
Koncepcja jest podobna, lecz metoda 'visit' w tym przypadku wybiera jedynie inną, prywatną metodę o nazwie składającej się z nazwy klasy odwiedzanej, np. 'visitNazwaKlasyElementu'. Do tej metody została przeniesiona cała logika wykonawcza. Dzięki temu osobne metody zajmują się osobnymi częściami roweru.
Ten dość ciekawy wzorzec projektowy Å‚atwo znajdzie także zastosowania przy tworzeniu aplikacji WWW. WszÄ™dzie tam, gdzie musimy wykonać seriÄ™ operacji na danej strukturze danych możemy Å›miaÅ‚o go implementować. PrzykÅ‚adem mógÅ‚by być koszyk z zakupami w sklepie on-line. Obiekt koszyka (np. iterator) zawiera w sobie obiekty różnego typu, ale posiadajÄ…ce wspólny interfejs. Klasa koszyka peÅ‚niÅ‚aby funkcjÄ™ "magazynu" (roweru, czy domu z poprzednich przykÅ‚adów) implementujÄ…c interfejs Visitable. Wizytatorem mógÅ‚by być obiekt podsumowujÄ…cy zamówienie w sklepie. OdwiedzaÅ‚by każdy element w koszyku i pobierajÄ…c cenÄ™ każdego z nich wyliczaÅ‚ kwotÄ™ zapÅ‚aty. Kolejnym mógÅ‚by być mechanizm sprawdzania dostÄ™pnoÅ›ci produktów w koszyku w magazynie. MożliwoÅ›ci jest wiele.
Waszym zdaniem:
Nikt jeszcze nie dodał swojego komentarza. Możesz być pierwszy!