PHP Patterns: Observer Pattern

Das Observer/Subject Pattern ist vom theoretischen recht klar, doch wie setzte ich so ein Pattern in der Praxis ein. Zuerst will ich das klassische Pattern an einem Beispiel aus meinem Bereich vorstellen und danach dieses Pattern erweitern.

Zuerst benötigen wir zwei Interfaces: Observer, Observable:

interface Observer {
	public function update(Observable $observable);
}
interface Observable {
	public function attach(Observer $observer);
	public function detach(Observer $observer);
	public function notify();
}

Dem Observable können mit attach/detach Observers hinzugefügt und entfernt werden.

Hier folgendes Szenario: Ich habe ein Product Object welches die Eigenschaften Titel, Beschreibung, Preis etc hat. Sobald ich eines von diesen Eigenschaften ändere, soll das Object ‚dirty‘ gesetzt werden und es soll zu den entsprechenden Webseiten exportiert werden. (Wir gehen davon aus, das ich das Produkt irgendwo in einem Admin/Backend System geändert habe).

Hier also unsere Product Klasse welche die Schnittstelle Observable und die entsprechenden Getter/Setter für title implementiert

public class Product implements Observable {
	protected $title;
	private $_dirty;
 
	public function setTitle($title) {
		if($this->getTitle() !== $title) {
			$this->_dirty = true;
			$this->title = $title
		}
	}
	public function getTitle() {
		return $this->title;
	}
	public function save() {
		if($this->_dirty) {
			$this->notify();
			$this->_dirty = false;
		}
	}
	public function attach(Observer $observer) {
		$this->observers[] = $observer;
	}
 
	public function detach(Observer $observer) {
		if(in_array($observer, $this->observers)) {
			$key = array_search($observer, $this->observers);
			unset($this->observers[$key]);
		}
	}
 
	public function notify() {
		if(count($this->observers) == 0)
			return;
 
		foreach ($this->observers as $observer) {
			$observer->update($this);
		}
	}
}

Wird der Titel verändert, wird die Eigenschaft _dirty auf true gesetzt und beim nächsten Speichern alle Observer informiert.

Hier also unser Observer:

class CustomProductObserver implements Observer {
	public function update($this) {
		echo "Observer " . __CLASS__ ." wurde augerufen";
	}
}

Wie sieht jetzt der Aufruf aus?

$product = new Product();
$customObserver = new CustomProductObserver();
$product->attach($customObserver);
$product->setTitle("Test");
$product->save();

Ergebnis: „Observer CustomProductObserver wurde aufgerufen

Observer
Nehmen wir mal an das dem Product Object weitere 10 Observer zugeordnet werden. Daraus folgt das bei jedem Speichern 11 Observer aufgerufen werden, obwohl diese gar nicht jedes mal informiert werden müssen. Ich möchte also Observer die nur bei bestimmten Notifies reagieren und antworten. Die Idee ist also in der Product::notify() Methode einen Namen zu übergeben.

// Notificationname wird vorher als Konstante definiert
define('NamespaceProductDidChangedNotification','ProductDidChanged');
$this->notify(NamespaceProductDidChangedNotification)

Nun ändern wir die notify Methode in folgende:

// Das Interface Observable muss entsprechend angepasst werden
public function notify($notificationName) {
	echo "$notificationName posted";
 
	if(count($this->observers) == 0)
		return;
 
	foreach ($this->observers as $observer) {
		if(method_exists($observer, $notificationName)) {
			$observer->$notificationName($observable);
		}
	}
}

Die Methode schaut ob die Observer die Methode ProductDidChanged implementiert hat. Falls das der Fall ist, wird diese mit dem Observable Object aufgerufen.

Das hat jetzt den entscheidenden Vorteil das ich überall in der Product Klassen Notifications posten kann. zB.

$this->notify(NamespaceProductBeforeSaveNotification);
$this->notify(NamespaceProductPriceDidChangedNotification);

Man erkennt die gegebene Konvention:

Konstante = Namespace+Klassennamen+HinweisName+“Notification“
Bsp: CompanyProductBeforeSaveNotification

Methode = Klassennamen+HinweisName
Bsp: ProductBeforeSave()

Natürlich könnte man das auch über Klassenkonstanten lösen:

// Beispiel:
const BeforeSaveNotification = "ProductBeforeSave"
echo self::BeforeSaveNotification;

Wer sich etwas mit Cocoa und Objectiv-C auskennt wird erkennen, das diese Pattern und Namen Konvention in einer ähnlichen Form dort vorkommt. Stichwort: Delegates und NotificationCenter.

Ich hoffe ich konnte einigermaßen ein Beispiel für das Observer Pattern geben. Es soll auch eher als Inspiration gesehen werden und nicht als 1:1 Tutorial welches abgearbeitet werden kann.

3 Comments

[…] more here: PHP Patterns: Observer Pattern Related ArticlesBookmarksTags PHP PHP is a computer scripting language. Originally […]

Suchmaschinen - AlexanderSeptember 24th, 2009 at 12:02 am

Hallo, sehr schöner Artikel.

In PHP gestaltet sich das Observern ja wirklich einfach, hätte ich nicht gedacht.
Viele Grüße,

Alexander

[…] dabei an das Observer Pattern gedacht und mir angeschaut, wie man dieses in PHP umsetzen kann, (PHP Patterns: Observer Pattern | Floriansweb) aber so richtig hat mir das nichts genützt. Auch habe ich versucht die Methode mit "<?php […]

Leave a comment

Your comment