Wzorzec obserwator
Wzorzec obserwator to moim zdaniem jeden z ciekawszych wzorców jakie wymyślono. Korzystam z niego od dłuższego czasu i muszę powiedzieć jest bardzo przydatny. Bardzo pomaga w wyodrębieniu funkcjonalności oraz zapobiega wrzucania wszystkiego do jednego wora.
Zastosowanie
Wzorzec obserwator stosujemy po to, by nie wrzucać w jedno miejsce masy funkcjonalności. Z racji, iż nie jestem mocny w opisywaniu tego typu rzeczy, posłuże się następującym przykładem:
Załóżmy, iż mamy obiekt Aktualności. Na stronie głównej wyświetlamy 5 ostatnich aktualności. Liste tę cache'ujemy, by nie biegać za każdym razem do bazy. Jednak by lista ta była akutalna, należy odnowić cache w momencie pojawienia się nowej aktualności. W tym celu piszemy obserwator, który obserwować będzie obiekt Aktualności i gdy jakaś aktualność zostanie dodana, wyczyści cache.
Zapytacie pewnie: Ale po co robić na tę okazję obserwator, nie można po prostu w klasie aktualności wyczyścić cache, zaraz po dodaniu nowego newsa?
Można i tak, ale posłuże się tu kolejnym przykładem z mego życia programisty wziętym:
Napisałem własną aplikację. Pisałem jednak ją tak, by zawierała tylko podstawową funkcjonalność (tzw. core). Reszta "bajerów" umieszczana jest w modułach. Wgrywając aplikację dla klienta, w zależności od jego wymagań, ładuje tylko określone moduły. No i mam przykładowo moduł cms, który odpowiada za zarządzanie treścią w serwisie. Mam też moduł news, który po instalacji modyfikuje lekko bazę, dzięki czemu moduł cms oprócz zwykłych stron, może też tworzyć aktualności.
Moduł news wyświetla na stronie głównej listę ostatnich pięciu newsów i listę te cache'uje. Musimy jednak po dodaniu newsa, wyczyśćić cache tej listy. Jednak dodawanie strony (w tym również newsa) odbywa się w module cms, do którego moduł news jako taki nie ma dostępu. Jeśli byśmy chcieli zrobić to po "normalnemu", musielibiśmy ingerować w kod modułu cms, by wyczyść cache dla modułu news - mało eleganckie rozwiązanie.
I tu z pomocą przychodzi wzorzec obserwator. Moduł news dostarcza własnego obserwatora, który obserwuje obiekt modułu cms. Gdy moduł cms utworzy stronę typu news, obserwator to wychwyci i sam wyczyści cache dla swojego modułu.
Wszelkie artykuły jakie czytałem odnośnie tego wzorca, bazowały na tym, iż mając obiekt, który chcemy obserwować, podczepiamy dla niego obiekt obserwatora. Wymagało to od nas za każdym razem tworzenia obiektu obserwowanego oraz obiektu obserwatora, nawet jeśli w danej chwili ani obiekt obserwowany, ani obiekt obserwatora nie był wykorzystywany. Posługując sie wcześniej podanym przykładem: przecież nie zawsze musi być tworzony obiekt strony ani obserwator, który go obserwuje. Nie zawsze przecież jest dodawana nowa strona i nie ma sensu za każdym razem tworzyć obiektu obserwatora. A przecież nasza aplikacja nie będzie się składała tylko z jednego obserwowanego obiektu ani z jednego obserwatora. Przy większej aplikacji będzie tych obiektów cała masa. Ładowanie dla nich wszystkich klas i ich tworzenie nie jest ani wygodnym, ani optymalnym rozwiązaniem.
Przedstawię tu mój sposób na wykorzystanie tego wzorca:
Najpierw stwórzmy klasę, która będzie zarządzała naszymi obserwatorami, rejestrowała ich, informowała o zmianach w obserwowanych obiektach i w razie potrzeby tworzyła obiekt obserwatora. Rejestrując obserwator będziemy tylko podawali nazwę klasy obserwatora oraz nazwę klasy obiektu obserwowanego. Zauważcie, że w momencie rejestracji obserwatora nie będziemy podawać stworzonych obiektów a tylko ich definicje. Dzięki temu nie będziemy potrzebować w momencie rejestracji żadnego z obiektów co zaoszczędzi nam trochę zasobów.
Zapomniałem dodać, iż rozszerzyłem trochę możliwość obserwowania obiektów o kody jakie one rzucają. Będziemy więc mogli powiedzieć, iż obserwujemy taki a taki obiekt, ale reagujemy tylko wówczas, gdy rzuci taki a taki kod. Poniżej podaję tylko definicję naszej klasy. Kod będzie dostępny w paczce.
<?php
class ObserverManager{
protected static $_registeredObservers = array();
/**
*
* @param $observer string|ObserverInterface obiekt obserwatora - może to być nazwa klasy obserwatora lub obiekt
* @param $className - nazwa klasy, jaką ma obserwować ten obserwator. Może być '*' - obserwuje wówczas wszystkie klasy
* @param $codes - tablica kodów, dla których ma się włączyć obserwator. Gdy nie podamy to będzie dla obojętnie jakiego
*/
public static function registerObserver($observer, $className, $codes = null){}
/**
* Poinformowanie zarejestrowanych obserwatorow o zdarzeniu
*
* @param $object obiekt, który rzuca notify
* @param $code - kod jaki rzuca. może być null, czyli nie rzuca żadnym kodem
* @return boolean
*/
public static function notify($object, $code = null){}
}
?>
Teraz zajmiemy się obserwatorem. Obserwator musi implementować następujący interfejs:
<?php
interface ObserverInterface {
/**
*
* @param $object obiekt obserwowany
* @param $code kod, jaki rzucił
* @return boolean
*/
public function update($object, $code = null);
}
?>
Metodę update będzie wywoływana przez obiekty obserwowane. A oto przykładowy obserwator, który będzie reagował na kody class1_remove oraz class1_update rzucane przez obiekt klasy Class1:
<?php
class Observer1 implements ObserverInterface {
public function __construct(){
echo '<b>Obserwator1</b> - teraz sie utworzylem <br />';
}
public function update($object, $code = null){
switch ($code) {
case 'class1_remove':
echo '<b>Obserwator1</b> - obiekt klasy class1 o ID <b>'.$object->id.'</b> wykonal akcje usuniecia<br />';
break;
case 'class1_update':
echo '<b>Obserwator1</b> - obiekt klasy class1 o ID <b>'.$object->id.'</b> wykonal akcje update\'u<br />';
break;
}
return true;
}
}
?>
Mając obserwatora, musimy go zarejestrować. W tym celu należu podać jakiej klasy obiekty będzie obserwował oraz podać ewentualnie kody, jakie będą go interesowały:
<?php
ObserverManager::registerObserver('Observer1', 'Class1', array('class1_remove','class1_update'));
?>
Możemy też powiedzieć, by obserwator reagował na każdą zmianę klasy niezależnie od rzuconego kodu:
<?php
ObserverManager::registerObserver('Observer1', 'Class1');
?>
lub też możemy sami stworzyć obiekt obserwatora i zamiast jego nazwy klasy podać gotowy już obiekt:
<?php
require_once('./observers/Observer1.class.php');
$observer1 = new Observer1();
ObserverManager::registerObserver($observer1, 'Class1');
?>
Wstawiając '*' za nazwe klasy obserwowanej powiemy, iż dany obserwator będzie obserwował wszystkie obiekty. W paczce macie na to również przykład.
Mając już obserwatorów, przejdźmy do klas, które będą obserwowane. Będą to normalne klasy, które w odpowiednim momencie poinformują ewnetualne obserwatory, że coś zrobiły.
<?php
class Class1 {
public $id;
public function __construct($id){
$this->id = $id;
}
public function remove(){
echo '<b>Class1</b> - robie akcje usuniecia. Moje ID to <b>'.$this->id.'</b> <br />';
//poinformowanie ewentualnych obserwatorów, że właśnie coś zrobiłem
//podajemy kod, by obserwator wiedział co dokładnie zostało wykonane i mógł odpowiednio zareagować
ObserverManager::notify($this, 'class1_remove');
}
public function update(){
echo '<b>Class1</b> - robie akcje update. Moje ID to <b>'.$this->id.'</b> <br />';
//poinformowanie ewentualnych obserwatorów, że właśnie coś zrobiłem
//podajemy kod, by obserwator wiedział co dokładnie zostało wykonane i mógł odpowiednio zareagować
ObserverManager::notify($this, 'class1_update');
}
}
?>
I koniec.
Paczka z opisanymi tu przykładami oraz paroma innymi znajduje się w dziale download. Mam nadzieję, że artykuł bardziej Wam pomógł niż namotał w głowach