- Publish-subscribe
-
Der Observer (Beobachter, Listener) ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung und gehört zu der Kategorie der Verhaltensmuster (Behavioural Patterns). Es dient zur Weitergabe von Änderungen an einem Objekt an von diesem Objekt abhängige Strukturen. Das Muster ist eines der sogenannten GoF-Muster (Gang of Four; siehe Viererbande).
Dieses Entwurfsmuster ist auch unter dem Namen publish-subscribe bekannt, frei übersetzt „veröffentlichen und abonnieren“.
Inhaltsverzeichnis
Verwendung
Anwendungsbeispiel
Eine oder auch mehrere Komponenten stellen den Zustand eines Objektes grafisch dar. Sie kennen die gesamte Schnittstelle dieses Objektes. Ändert sich der Zustand des Objektes, müssen die Komponenten darüber informiert werden. Andererseits soll das Objekt aber von den Komponenten unabhängig bleiben - ihre Schnittstelle also nicht kennen.
Beispiel: Messergebnisse werden gleichzeitig in einem Balkendiagramm, einem Liniendiagramm und einer Tabelle dargestellt. Messwerte ändern sich permanent. Die Komponenten der Diagramme sollen diese Änderungen permanent darstellen, das gemessene Objekt soll dabei aber keine Kenntnis über die Struktur dieser Komponenten besitzen.
Lösung
Das beobachtete Objekt bietet einen Mechanismus, um Observer an- und abzumelden und diese über Änderungen zu informieren. Es kennt alle seine Observer nur über die (überschaubare) Schnittstelle Observer. Es meldet jede Änderung völlig unspezifisch an jeden angemeldeten Observer, braucht also die weitere Struktur dieser Komponenten nicht zu kennen.
Die Beobachter implementieren ihrerseits eine (spezifische) Methode, um auf die Änderung zu reagieren. In der Regel werden die für eine Komponente relevanten Teile des Zustands abgefragt.
Allgemeine Anwendungssituationen
Allgemein finden Observer Anwendung, wenn eine Abstraktion mehrere Aspekte hat, die von einem anderen Aspekt derselben Abstraktion abhängen, die Änderung eines Objekts Änderungen an anderen Objekten nach sich zieht oder ein Objekt andere Objekte benachrichtigen soll, ohne diese im Detail zu kennen.
UML-Diagramm
Das folgende Klassendiagramm zeigt die am Entwurfsmuster beteiligten Rollen. Das Subjekt kann mehrere Beobachter haben, die unterschiedlichen konkreten Klassen angehören können.
Akteure
Ein Subjekt (Beobachtbares Objekt, auch Publisher, also „Veröffentlicher“, genannt) kennt eine Liste von Observern, aber keine konkreten Observer. Es bietet eine Schnittstelle zur An- und Abmeldung von Observern und eine Schnittstelle zur Benachrichtigung von Observern über Änderungen an. Ein Konkretes Subjekt (Konkretes, beobachtbares Objekt) speichert den relevanten Zustand und benachrichtigt alle Observer bei Zustandsänderungen über deren Aktualisierungsschnittstelle. Es verfügt über eine Schnittstelle zur Erfragung des aktuellen Zustands.
Die Beobachter (Observer auch Subscriber, also „Abonnent“, genannt) definieren eine Aktualisierungsschnittstelle. KonkreteBeobachter verwalten die Referenz auf ein konkretes Subjekt, dessen Zustand sie beobachten und speichern dessen Zustand konsistent. Sie implementieren eine Aktualisierungsschnittstelle unter Verwendung der Abfrageschnittstelle des konkreten Subjekts.
Vorteile
Subjekte und Observer können unabhängig variiert werden. Subjekt und Benutzer sind auf abstrakte und minimale Art lose gekoppelt. Das beobachtete Objekt braucht keine Kenntnis über die Struktur seiner Observer zu besitzen, sondern kennt diese nur über die Observer-Schnittstelle. Ein abhängiges Objekt erhält die Änderungen automatisch. Multicasts werden unterstützt.
Nachteile
Änderungen am Objekt führen bei großer Observeranzahl zu hohen Änderungskosten. Einerseits informiert das Subjekt jeden Observer, auch wenn dieser die Änderungsinformation nicht benötigt. Zusätzlich können die Änderungen weitere Änderungen nach sich ziehen und so einen unerwartet hohen Aufwand haben.
Ruft ein Observer während der Bearbeitung einer gemeldeten Änderung wiederum Änderungsmethoden des Subjektes auf, kann es zu Endlosschleifen kommen. Der Mechanismus liefert keine Information darüber, was sich geändert hat. Die daraus resultierende Unabhängigkeit der Komponenten kann sich allerdings auch als Vorteil herausstellen.
Bei der gerade durchgeführten Observierung eines Objektzustands kann es notwendig sein, einen konsistenten Subjektzustand zu garantieren. Dies kann durch synchrone Aufrufe der Notifizierungsmethode des Observers sichergestellt werden. In einem Multithreading System sind evtl. Lockingmechanismen oder Threads mit queuing zur Observer-Notifizierung erforderlich.
Beispiel
Implementierung in C++
Diese Implementierung erlaubt es nicht, während einer Benachrichtigung (Notification) im gleichen Thread einen Observer zu entfernen, weil dadurch die Iteratoren ungültig werden. Eine Möglichkeit zum Lösen dieses Problems ist, „abgehängte“ (detached) Observer in einer eigenen Liste zu verwalten und nicht direkt aus der Observer-Liste zu entfernen. Dann wirkt sich das Entfernen erst bei einer erneuten Benachrichtigung aus. Dabei müsste in der Benachrichtigungsschleife geprüft werden, ob der Observer nicht bereits detached wurde. Gleiches gilt auch für das Hinzufügen weiterer Observer.
class CObserver { public: virtual void NotifyStateChange( void ) = 0 ; } ; class CObserverSubject { private: typedef CObserver tObserver ; typedef std::deque< tObserver* > tOBSERVERS ; tOBSERVERS Observers ; public: bool AddObserver( tObserver* Observer ) { tOBSERVERS::const_iterator foundObserver = std::find( this->Observers.begin(), this->Observers.end(), Observer ) ; if( foundObserver == this->Observers.end() ) { this->Observers.push_back( Observer ) ; return true ; } else { return false ; } } bool RemoveObserver( tObserver* Observer ) { tOBSERVERS::iterator foundObserver = std::find( this->Observers.begin(), this->Observers.end(), Observer ) ; if( foundObserver != this->Observers.end() ) { this->Observers.erase( foundObserver ) ; return true ; } else { return false ; } } void NotifyStateChange() { tOBSERVERS::const_iterator allObservers = this->Observers.begin() ; while( allObservers != this->Observers.end() ) { ( *allObservers )->NotifyStateChange() ; allObservers++; } } } ;
Observierung in Objective-C und Cocoa
Das Entwurfsmuster ist bereits als Kategorie von NSObject implementiert, so dass seitens des Observierten kein Aufwand anfällt. Der Observierer muss sich lediglich anmelden und eine Notifizierungsmethode implementieren:
// Anmeldung der Observierung [theObserved addObserver:self forKeyPath:@"attribute" options:NSKeyValueObservingOptionNew context:'WIKI']; // Abarbeitung - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
Dabei ist zu beachten, dass im Gegensatz zum obigen Code in C++ die Observierung für ein bestimmtes Attribut erfolgt, also wesentlich weniger Kosten verursacht. Die Auslösung der Observierung muss zudem nicht manuell erfolgen, sondern wird vom System transparent erledigt, sobald ein Setter für das entsprechende Attribut aufgerufen wird. Daher führt jede Änderung automatisch zur Notifizierung:
// Irgendwo in den Untiefen des Codes [anObject setAttribute:@"some data"]; // Die Observierung von attribute wird ausgelöst
Hierzu wird eine Technologie namens is-a-Swizzling angewendet.
Implementierung in Python
class Subject: _observers = [] def register_observer(self,observer): self._observers.append(observer) def remove_observer(self,observer): self._observers.remove(observer) def set_value(self,value): self.value = value self.inform_observers() def inform_observers(self): for observer in self._observers: observer.update(self.value) class Observer1: def __init__(self,subject): subject.register_observer(self) def update(self,value): print "I'm observer 1 and subject value is "+str(value) class Observer2: def __init__(self,subject): subject.register_observer(self) def update(self,value): print "I'm observer 2 and subject value is "+str(value) if __name__ == "__main__": subject = Subject() observer1 = Observer1(subject) observer2 = Observer2(subject) subject.set_value(1) subject.remove_observer(observer1) subject.set_value(3)
Implementierung in PHP5
interface Subject{ public function register_observer(Observer $observer); public function remove_observer(Observer $observer); public function inform_observers(); } class MySubject implements Subject{ private $observers = array(); private $value; public function register_observer(Observer $observer){ $this->observers[] = $observer; } public function remove_observer(Observer $observer){ $offset = array_search($observer, $this->observers); $this->observers = array_splice($this->observers, $offset, 1); } public function inform_observers(){ foreach($this->observers as $observer) { $observer->update($this->value); } } public function setValue($value){ $this->value = $value; $this->inform_observers(); } } interface Observer{ public function update($value); } class MyObserver1 implements Observer { public function __construct(Subject $subject){ $subject->register_observer($this); } public function update($value){ print "I'm observer 1 and value is ".$value."\n"; } } class MyObserver2 implements Observer { public function __construct(Subject $subject){ $subject->register_observer($this); } public function update($value){ print "I'm observer 2 and value is ".$value."\n"; } } $mySubject = new MySubject(); $myObserver1 = new MyObserver1($mySubject); $myObserver2 = new MyObserver2($mySubject); $mySubject->setValue(1); $mySubject->remove_observer($myObserver1); $mySubject->setValue(2);
I'm observer 1 and value is 1 I'm observer 2 and value is 1 I'm observer 2 and value is 2
Implementierung in JavaScript
<html><head><script type="text/javascript"> function Subject () { this.observers = new Array(); this.register_observer = function(observer){ this.observers.push(observer); } this.remove_observer = function(observer){ for(var i=0; i < this.observers.length; i++){ if(this.observers[i] === observer){ this.observers.splice(i, 1); } } } this.set_value = function(value){ this.value = value this.inform_observers() } this.inform_observers = function(){ for each (var observer in this.observers){ observer.update(this.value) } } } function Observer1 (subject){ subject.register_observer(this); this.update = function(value){ document.write("I'm observer 1 and subject value is "+value+"<br>\n"); } } function Observer2 (subject){ subject.register_observer(this); this.update = function(value){ document.write("I'm observer 2 and subject value is "+value+"<br>\n"); } } </script> </head> <body> <script type="text/javascript"> var subject = new Subject(); var observer1 = new Observer1(subject); var observer2 = new Observer2(subject); subject.set_value(1); subject.remove_observer(observer1); subject.set_value(3); </script></body></html>
Implementierung in Java
Java bietet eine fertige Observer-Schnittstelle und eine Observable-Klasse an, die der Entwickler verwenden kann:
package model; import java.util.Observable; public class Model extends Observable { private String property; public String getProperty() { return property; } public void setProperty(String property) { this.property = property; setChanged(); notifyObservers("property"); } public Model() { } } package view; import java.util.Observable; import java.util.Observer; import model.Model; public class View implements Observer { private Model m; public View(Model m){ this.m = m; m.addObserver(this); } public void update(Observable arg0, Object arg1) { if ((arg0 == m) && "property".equals(arg1)){ System.out.println(m.getProperty()); } } }
Diese Variante hat die “Unschönheit”, dass das Model von der Klasse Observable erben muss und somit keine andere Oberklasse haben kann. Dies ist dann nicht relevant, wenn alle Model-Klassen von einer gemeinsamen Oberklasse (direkt oder indirekt) erben (müssen), die dann von Observable abgeleitet sein kann/muss.
Eine Alternative wäre es, ein Observable-Interface bereitzustellen, das vom Model implementiert wird. Dann muss aber das Model die Verwaltungsmethoden add/remove selber bereitstellen. Deren Implementierung kann jedoch wieder über die Klasse Observable (oder einer Unterklasse davon – da setChanged protected ist!) erfolgen:
package observer; import java.util.Observer; public interface Observable { void addObserver(Observer o); void deleteObserver(Observer o); void deleteObservers(); boolean hasChanged(); int countObservers(); } package observer; import java.util.Observable; public class ObservableSupport extends Observable { public ObservableSupport() { } public void markAndNotify(Object o){ setChanged(); notifyObservers(o); } } package model; import java.util.Observer; import Observable; import ObservableSupport; public class Model2 implements Observable { private ObservableSupport support; private String property; public String getProperty() { return property; } public void setProperty(String property) { this.property = property; support.markAndNotify("property"); } public Model2() { support = new ObservableSupport(); } public void addObserver(Observer o) { support.addObserver(o); } public int countObservers() { return support.countObservers(); } public void deleteObserver(Observer o) { support.deleteObserver(o); } public void deleteObservers() { support.deleteObservers(); } public boolean hasChanged() { return support.hasChanged(); } }
Eine weitere Alternative ist die Verwendung eines PropertyChangedListeners, der speziell für Änderungen von Attributwerten, insb. der Properties im JavaBean-Komponentenmodell, entwickelt wurde:
package view; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import Model3; public class View3 implements PropertyChangeListener { private Model3 m; public View3(Model3 m){ this.m = m; m.addPropertyChangeListener(this); } public void propertyChange(PropertyChangeEvent evt) { if ((evt.getSource() == m) && "property".equals(evt.getPropertyName())){ System.out.println(evt.getNewValue()); } } } package model; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; public class Model3{ private PropertyChangeSupport support; private String property; public String getProperty() { return property; } public void setProperty(String property) { String oldValue = this.property; this.property = property; support.firePropertyChange( "property", oldValue, property); } public Model3() { support = new PropertyChangeSupport(this); } public void addPropertyChangeListener( PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } public void removePropertyChangeListener( PropertyChangeListener listener) { support.removePropertyChangeListener(listener); } }
Verwandte Entwurfsmuster
Ein Vermittler kann zwischen Subjekten und Observern vermitteln.
Siehe auch
Erzeugungsmuster: Abstrakte Fabrik | Singleton | Builder | Fabrikmethode | Prototyp
Strukturmuster: Adapter | Brücke | Decorator | Facade | Flyweight | Kompositum | Stellvertreter
Verhaltensmuster: Observer | Visitor | Interpreter | Iterator | Kommando | Memento | Schablonenmethode | Strategie | Vermittler | Zustand | Zuständigkeitskette
(Klassenmuster sind kursiv dargestellt)
Wikimedia Foundation.