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

Komentarze

 

2009-03-28 01:27 gość_orglee

Nie wiem czy znasz framework Kohana, ale czy nie nazwałbyś zarządzania zdarzeniami w nim implementacją tego wzorca tylko pod zmienioną nazwą?

2009-03-28 13:46 nospor

W życiu na oczy nie widziałem Kohany, więc trudno mi się ustosunkować na Twój komentarz.

2009-06-11 13:03 gość_wookieb

W rzeczywistości to o czym mówisz jest to taki EventDispatcher. Niestety ludzie (nie chodzi tutaj o ciebie) tworzą bardzo lekkie odmiany znanych wzorców i odrazu tworzą ich nowe nazwy.

Długo zastanawiałem się czy wprowadzić EventDispatchera do swoich aplikacji i jestem na plus.
Zobacz jak działą EventDispatcher we Flashu (ActionScript)
Powoli odchodzę od klas statycznych więc we flashu jest to bardzo fajnie rozwiązane. Polecam

2009-06-11 13:48 nospor

Hihi, no popatrz, używam wzorców i nie wiem, że ich używam
Ale to juz nie pierwszy raz.

Wzorzec ten jest naprawde bardzo fajny i pożyteczny. Używam go namiętnie i się sprawdza.

2009-06-24 00:04 gość_Tomasz

Bardzo jasno wytłumaczone, dzięki serdeczne. Na podstawie książki "PHP5. Zaawansowane programowanie." nie mogłem chwycić a tu proszę - jasno wytlumaczone

2010-07-27 22:56 gość_cojack

wookieb to nie jest dyspozytor, ale własna wersja obserwatora by nospor ;D Dyspozytora to masz ładnie na moim blogu opisanego. Sory że odgrzewam kotlety, ale wcześniej nie czytałem Twojego bloga nospor.

2010-07-27 23:30 nospor

Dyspozytora to masz ładnie na moim blogu opisanego
Ktoś ci tam w komentarzach w pozycjonowanie się bawi. Usunąłbyś dla przyzwoitości by se nie myśleli tacy, że bezkarnie mogą spamować

Co do arta to chętnie jutro poczytam.

Sory że odgrzewam kotlety,
Komentarze zawsze mile widziane. Można się zawsze o coś "pokłócić"

2010-07-28 10:41 gość_cojack

Połączyłeś dwa wzorce do wykorzystania w bardzo przyzwoitej formie obserwatora, hmmm na prawdę ciekawe rozwiązanie, będę musiał to przemyśleć głębiej nad zastosowaniem Twojego przykładu w moim fw bo już obserwatora mam zaimplementowanego, nie lubie registry ;p Trzymanie obiektów klas w klasie blee

2010-07-28 11:37 nospor

nie lubie registry ;p Trzymanie obiektów klas w klasie blee
Rejestr z obserwatorem ma niewiele wspólnego, żeby nie powiedzieć nic. Zakładam, że to znowu luźne odejście od tematu
Ja tam lubię rejestr i go używam. W żaden sposób nie kłóci się on z podanym tutaj obserwatorem.

2010-07-28 13:16 gość_cojack

ObserverManager::registerObserver();

a to nie jest wzorzec registry?

2010-07-28 13:33 nospor

@cojak to że jakaś metoda ma w nazwie tekst "register" to nie znaczy, że używa wcorca Registry

Dodaj komentarz

 

Dostępne bbcode: b, u, i, url, code, php, css, html, sql, js

Ostatnio komentowane

  1. Pager 2.5.1 oraz EPa... Na szybko2
  2. Pager 2.5.1 oraz EPa... Sławek
  3. Mysql - FAQ Piotr
  4. Liczba dni roboczych Na szybko2
  5. Liczba dni roboczych Naszybko
  6. Klasa widoku nospor
  7. Klasa widoku freebox

Ostatnio na forum

  1. programista php-webm... pracamatysart
  2. Programista PHP/ Mag... Create Magento 2 Marketplace
  3. Baza Danych gosc
  4. Baza Danych YankeS
  5. Baza Danych gosc
  6. Baza Danych YankeS
  7. Problem z bazą danyc... Baza Danych

Skrypty użytkowników

  1. Klasa obsługi szablo... Lirdoner
  2. Sekcje user76
  3. Klasa walidująca for... user76
  4. Licznik Gości online korey
  5. Form Builder Comandeer
  6. Dynamiczny licznik z... korey
  7. Captcha Comandeer