Ostatnio sporo czytam na temat programowania obiektowego. W niemal każdej książce, która wpadła mi w łapki, przykłady wzorców projektowych podane są w języku Java. Wyjątkiem jest Programowanie obiektowe w PHP 5 Hasina Haydera.
Do napisania postu skłoniły mnie pojawiające się gdzieniegdzie narzekania programistów na PHP, że jakoby ten język to zabawka, że nie da się w nim tworzyć w pełni obiektowego kodu itd. Dzisiaj zademonstruję działanie 3 wzorców projektowych na raz (Strategia, Obserwator, Łańcuch zobowiązań).
Chcemy zrobić coś takiego:

Mamy więc jeden obiekt, który zawiera w sobie dane na temat obiektów, które chce powiadamiać, że coś się u niego zmieniło. Tak działa typowy wzorzec obserwator. Ja to jednak zmodyfikowałem tak, że pierwszy obserwator w łańcuchu otrzymuje informację o zmianie stanu obiektu obserwowanego. Jeżeli umie sobie poradzić z przekazanym komunikatem — reaguje. Jeżeli ta informacja go zbytnio nie interesuje — przekazuje następnemu obiektowi w łańcuchu. Tak działa wzorzec łańcuch zobowiązań. Po co mi wzorzec strategia? Z czystego lenistwa
Po co pisać kilka rodzajów obserwatorów do testu, które reagują w inny sposób na komunikaty, skoro można implementować interfejs reagujący na różne komunikaty. Jeżeli będę chciał testować inny rodzaj komunikatów to napiszę sobię nową implementację interfejsu reagującego i tyle
Bierzemy się więc do roboty. Najpierw tworzymy sobie 2 interfejsy. 1 dla wzorca obserwator, drugi dla łańcucha zobowiązań.
interface Obserwujacy { public function powiadomienie($komunikat); } interface Lancuch { public function zareaguj($komunikat); }
Następnie tworzymy sobie jakiś obiekt obserwowany:
class Obserwowany { private $obserwatorzy = array(); private $komunikat; public function addObserwujacy(Obserwujacy $o) { $this->obserwatorzy[] = $o; } public function setKomunikat($k) { $this->komunikat = $k; $this->powiadamiaj(); } private function powiadamiaj() { foreach ($this->obserwatorzy as $obserwator) { $obserwator->powiadomienie($this->komunikat); } } }
Obiekt ma zaledwie 3 metody. Pierwsza dodaje kolejnego obserwatora do listy, druga zmienia stan obiektu (wysyła komunikat wszystkim), trzecia dla każdego obserwatora wywołuje metodę powiadamiającą o zmianie stanu.
Następnie tworzymy klasę Obserwator, która reaguje na wszystkie komunikaty.
class Obserwator implements Obserwujacy , Lancuch { protected $reagujNa; // implements ReagujNa protected $nazwa; protected $lancuch; public function __construct($nazwa, Lancuch $l) { $this->reagujNa = new ReagujNaWszystko(); $this->nazwa = $nazwa; $this->lancuch = $l; } public function powiadomienie($komunikat) { $this->zareaguj($komunikat); } public function zareaguj($komunikat) { if ($this->reagujNa->reaguj($komunikat)) { echo '<strong>' . $this->nazwa . '</strong> otrzymal komunikat ' . $komunikat . ' i zareagowal.<br />'; } else { $this->lancuch->zareaguj($komunikat); } } }
Zmienna $reagujNa implementuje interfejs ReagujNa, który stworzymy poniżej. Niestety w PHP nie da się wymusić określonego typu zmiennej — w tym lepsza jest Java. Jeżeli ktoś koniecznie chce, to może sobie sprawdzać czy dana zmienna rzeczywiście implementuje interfejs przed wywołaniem.
interface ReagujNa { public function reaguj($komunikat); } class ReagujNaWszystko implements ReagujNa { public function reaguj($komunikat) { return true; } } class ReagujNaLiterki implements ReagujNa { public function reaguj($komunikat) { if (is_string($komunikat)) { return true; } else { return false; } } } class ReagujNaNumerki implements ReagujNa { public function reaguj($komunikat) { if (is_numeric($komunikat)) { return true; } else { return false; } } }
Mamy tutaj trzy klasy posiadające po jednej metodzie, które zwracają prawdę lub fałsz w zależności od podanego parametru. Dalej wypadałoby wykorzystać te klasy zmieniając zachowanie obiektu Obserwator.
class ObserwatorLiterek extends Obserwator { public function __construct($nazwa, Lancuch $l) { parent::__construct($nazwa,$l); $this->reagujNa = new ReagujNaLiterki(); } } class ObserwatorNumerkow extends Obserwator { public function __construct($nazwa, Lancuch $l) { parent::__construct($nazwa,$l); $this->reagujNa = new ReagujNaNumerki(); } }
Na koniec potrzebne jest nam jeszcze ostatnie ogniwo łańcucha, nazwane przeze mnie Limiter. Cokolwiek limiter dostaje — wyrzuca na wyjściu i tyle.
class Limiter implements Lancuch { private $nazwa; public function __construct ($nazwa) { $this->nazwa = $nazwa; } public function zareaguj($komunikat) { echo '<strong>Limiter ' . $this->nazwa . '</strong> otrzymal komunikat ' . $komunikat . '<br />'; } }
Pora to wszystko przetestować. Tworzymy obiekt obserwowany,
$obserwowany = new Obserwowany();
a następnie 4 ciągi obserwatorów. Tworzymy go od końca. Coś jak Matrioszki.
// ciag 1 $limiter1 = new Limiter('ciagu 1'); $obserwatorWszystkiego = new Obserwator('Obserwator Wszystkiego ciagu 1',$limiter1); $obserwowany->addObserwujacy($obserwatorWszystkiego); // ciag 2 $limiter2 = new Limiter('ciagu 2'); $obserwatorLiterek = new ObserwatorLiterek('Obserwator Literek ciagu 2',$limiter2); $obserwowany->addObserwujacy($obserwatorLiterek); // ciag 3 $limiter3 = new Limiter('ciagu 3'); $obserwatorNumerkow = new ObserwatorNumerkow('Obserwator Numerkow ciagu 3',$limiter3); $obserwowany->addObserwujacy($obserwatorNumerkow); // ciag 4 $limiter4 = new Limiter('ciagu 4'); $obsAll = new Obserwator('Obserwator Wszystkiego ciagu 4',$limiter4); $obsNum = new ObserwatorNumerkow('Obserwator Numerkow ciagu 4',$obsAll); $obsLit = new ObserwatorLiterek('Obserwator Literek ciagu 4',$obsNum); $obserwowany->addObserwujacy($obsLit);
Ostatnim krokiem jest sprawdzenie jak to teraz działa.
$obserwowany->setKomunikat('abc'); echo '----------------------------------'; $obserwowany->setKomunikat(123);
Wynik:
Obserwator Wszystkiego ciagu 1 otrzymal komunikat abc i zareagowal.
Obserwator Literek ciagu 2 otrzymal komunikat abc i zareagowal.
Limiter ciagu 3 otrzymal komunikat abc
Obserwator Literek ciagu 4 otrzymal komunikat abc i zareagowal.
----------------------------------
Obserwator Wszystkiego ciagu 1 otrzymal komunikat 123 i zareagowal.
Limiter ciagu 2 otrzymal komunikat 123
Obserwator Numerkow ciagu 3 otrzymal komunikat 123 i zareagowal.
Obserwator Numerkow ciagu 4 otrzymal komunikat 123 i zareagowal.
A teraz wyjaśnienie jak to wszystko działa:
Ciąg 1 zawiera w sobie tylko obserwatora wszystkiego i limiter. Obserwator każdy komunikat traktuje jako informację dla siebie i nie przekazuje dalej do limitera (przez co komunikat nigdy do niego nie dojdzie).
Ciąg 2 ma tylko obserwatora literek i limiter. Na pierwszy, słowny komunikat zareagował. Na komunikat zawierający liczbę nie zareagował i przekazał dalej do limitera.
Ciąg 3 ma w zamian obserwatora numerków i limiter. Zachował się dokładnie odwrotnie od poprzedniego.
Ciąg 4 ma kolejno obserwatorów literek, numerków, wszystkiego i limiter. Teraz dokładnie widać jak to działa. Jeżeli dany obserwator nie znalazł informacji przydatnej dla siebie — po prostu przekazał następnemu obserwatorowi w ciągu. Warto zauważyć, że obserwator wszystkiego i limiter w tym przypadku nigdy nie zareaguje, gdyż komunikat zostanie rozwiązany zanim do niego dojdzie.
Dla chętnych zamieszczam plik wzorce.php (skasowali, a kopii nie mam
). Zachęcam do eksperymentów i ewentualnej przebudowy obserwatorów tak, aby reagowały na konkretne słowa lub liczby.
Podobne wpisy:
