Problem
Wenn ein Objekt seinen Zustand ändert, sollen davon abhängige Objekte benachrichtigt werden.
Motivation
Eine Änderung an einem Objekt erfordert Änderungen an anderen Objekten, um die Konsistenz des Gesamtsystems zu erhalten. An Stelle einer engen Kopplung wird eine lose Kopplung der Objekte angestrebt, bei der die beteiligten Objekte unabhängig voneinander variiert werden können.
Lösung
Das unter Beobachtung stehende Objekt, Subject genannt, stellt die folgenden Methoden zur Verfügung, über die sich andere Objekte, Observer genannt, für die Benachrichtigung bei Zustandsänderungen an- und abmelden können:
attach($observer)
Registriert das Objekt $observer zur
Benachrichtigung bei Änderung des Zustands des Objektes, dessen
attach()-Methode aufgerufen wird.
detach($observer)
Hebt die Registrierung des Objektes $observer
zur Benachrichtigung bei Änderung des Zustands des Objektes,
dessen detach()-Methode aufgerufen wird.
getState()
Liefert den aktuellen Zustand des Objektes. Ein Beobachter-Objekt kann sich so nach der Benachrichtigung über eine Änderung des Zustands des beobachteten Objektes darüber informieren, wie sich dessen Zustand geändert hat.
Die Benachrichtigung der Beobachter-Objekte erfolgt durch den Aufruf
der Methode notify() des Subject-Objektes. Diese Methode
ruft auf jedem als Beobachter registrierten Objekt dessen
update()-Methode auf, die von den Beobachter-Objekten
bereitzustellen ist.
Abbildung 7.1
zeigt zwei Klassen Subject und
Observer, die die beschriebene Funktionalität zur
Verfügung stellen und als Basis für zwei konkrete Klassen,
ConcreteSubject und
ConcreteObserver, dienen.
Beispiel 7.1: Die abstrakte Klasse Subject
<?php
require_once 'Observer.php';
abstract class Subject {
protected $observers = array();
public function attach(Observer $observer) {
$this->observers[] = $observer;
}
public function detach(Observer $observer) {
for ($i = 0; $i < sizeof($this->observers); $i++) {
if ($this->observers[$i] === $observer) {
unset($this->observers[$i]);
}
}
}
protected function notify() {
for ($i = 0; $i < sizeof($this->observers); $i++) {
$this->observers[$i]->update();
}
}
public abstract function getState();
}
?>Beispiel 7.2: Die abstrakte Klasse Observer
<?php
require_once 'Subject.php';
abstract class Observer {
protected $subject = NULL;
public function attach(Subject $subject) {
$this->subject = $subject;
$this->subject->attach($this);
}
public function detach() {
if ($this->subject !== NULL) {
$this->subject->detach($this);
}
}
public abstract function update();
}
?>Beispiel 7.3: Die Klasse ConcreteSubject
<?php
require_once 'Subject.php';
class ConcreteSubject extends Subject {
protected $state = NULL;
public function getState() {
return $this->state;
}
public function doSomething() {
// ...
$this->notify();
}
}
?>Beispiel 7.4: Die Klasse ConcreteObserver
<?php
require_once 'Observer.php';
class ConcreteObserver extends Observer {
protected $state = NULL;
public function __construct(Subject $subject) {
$this->attach($subject);
}
public function update() {
print 'ConcreteObserver::update()';
$this->state = $this->subject->getState();
}
}
?>Beispiel 7.5: Verwendung von Subject und Observer
<?php
require_once 'ConcreteSubject.php';
require_once 'ConcreteObserver.php';
$subject = new ConcreteSubject;
$observer = new ConcreteObserver($subject);
$subject->doSomething();
$observer->detach();
$subject->doSomething();
?>ConcreteObserver::update()
Anwendungsbeispiele
Das Problem, das ursprünglich das Beobachter-Muster motivierte, war die Kommunikation zwischen dem Model-Objekt und dessen View-Objekten in einer Applikation, die dem Model-View-Controller-Prinzip (MVC) folgt. Hierbei wird eine Anwendung in die drei Schichten Datenmodell (Model), Darstellungsschicht (View) und Steuerungsschicht (Controller) unterteilt. Diese Schichten sind voneinander entkoppelt: Das Datenmodell kennt weder Darstellungsschicht noch Steuerungsschicht. Die Darstellungsschicht registriert sich als Beobachter des Datenmodells und stellt dieses dar. Die Steuerungsschicht steuert den Ablauf der Anwendung und nimmt Änderungen am Datenmodell über dessen Programmierschnittstelle vor.
Ein anderes Einsatzgebiet des Beobachter-Musters ist das Protokollieren
von Abläufen. Für die Verfolgung, und damit für das Protokollieren,
der Testausführung bietet PHPUnit die Schnittstelle
PHPUnit2_Framework_TestListener an. Objekte von Klassen,
die diese implementieren, können über eine entsprechende Methode
(addListener($listener)) an ein Objekt der Klasse
PHPUnit2_Framework_TestResult "angehängt" werden, um
dieses zu beobachten und so die Testausführung zu protokollieren.
Beispiel 7.6: Eine Implementierung der Schnittstelle PHPUnit2_Framework_TestListener
<?php
require_once 'PHPUnit2/Framework/TestListener.php';
class SimpleTestListener
implements PHPUnit2_Framework_TestListener {
public function
addError(PHPUnit2_Framework_Test $test, Exception $e) {
printf(
'Bei der Ausführung des Testfalls "%s"' .
" trat ein Fehler auf.\n",
$test->getName()
);
}
public function
addFailure(PHPUnit2_Framework_Test $test,
PHPUnit2_Framework_AssertionFailedError $e) {
printf(
"Der Testfall \"%s\" schlug fehl.\n",
$test->getName()
);
}
public function
addIncompleteTest(PHPUnit2_Framework_Test $test,
Exception $e) {
printf(
"Der Testfall \"%s\" wurde nicht implementiert.\n",
$test->getName()
);
}
public function startTest(PHPUnit2_Framework_Test $test) {
printf(
"Ausführung des Testfalls \"%s\" wurde gestartet.\n",
$test->getName()
);
}
public function endTest(PHPUnit2_Framework_Test $test) {
printf(
"Ausführung des Testfalls \"%s\" wurde beendet.\n",
$test->getName()
);
}
public function
startTestSuite(PHPUnit2_Framework_TestSuite $suite) {
}
public function
endTestSuite(PHPUnit2_Framework_TestSuite $suite) {
}
}
?>
In Beispiel 7.7
wird zunächst ein neues Objekt der Klasse PHPUnit2_Framework_TestResult
erzeugt. Diesem wird im Anschluss ein Objekt der Klasse SimpleTestListener
aus Beispiel 7.6
als Listener hinzugefügt. Schließlich wird das PHPUnit2_Framework_TestResult-Objekt
genutzt, um den testDoSomething Testfall
(Beispiel 4.7)
auszuführen.
Beispiel 7.7: Ausführung eines Testfalls unter Beobachtung des SimpleTestListener
<?php
require_once 'PHPUnit2/Framework/TestResult.php';
require_once 'SampleTest.php';
require_once 'SimpleTestListener.php';
$test = new SampleTest('testDoSomething');
$result = new PHPUnit2_Framework_TestResult;
$result->addListener(new SimpleTestListener);
$result->run($test);
?>Ausführung des Testfalls "testDoSomething" wurde gestartet. Ausführung des Testfalls "testDoSomething" wurde beendet.