Observer Pattern: Was steckt hinter dem Observer Design Pattern?

Bei der Programmierung von Software gilt es, diverse Aspekte zu beachten: Das Endprodukt soll nicht nur über die gewünschten Funktionen, sondern auch über einen möglichst gut lesbaren und einfach nachzuvollziehenden Quellcode verfügen. Der hierfür anfallende Aufwand soll dabei so gering wie möglich ausfallen – insbesondere, wenn Programme oder Programmteile mit immer wiederkehrenden Funktionen oder Elementen entworfen werden. Mit den sogenannten GoF-Patterns („Gang of Four“) bzw. -Mustern existiert zu diesem Zweck eine Reihe vordefinierter Musterlösungen für diverse Problemstellungen der Software-Gestaltung.

Neben anderen bekannten Patterns wie dem Visitor Pattern oder dem Singleton Pattern zählt auch das sogenannte Observer Pattern zu dieser Sammlung aus praktischen Designmustern, deren Beachtung den Programmieralltag erheblich vereinfacht. Wir verraten, was es mit dem Oberserver Design Pattern auf sich hat (inklusive grafischer Darstellung in UML) und welche Stärken und Schwächen das Muster hat.

Was ist das Observer Pattern (Beobachter-Entwurfsmuster)?

Das Observer Design Pattern, kurz Observer Pattern bzw. deutsch Beobachter-Entwurfsmuster, ist eine der beliebtesten Mustervorlagen für das Design von Computersoftware. Es gibt eine einheitliche Möglichkeit an die Hand, eine Eins-zu-eins-Abhängigkeit zwischen zwei oder mehreren Objekten zu definieren, um sämtliche Änderungen an einem bestimmten Objekt auf möglichst unkomplizierte und schnelle Weise zu übermitteln. Zu diesem Zweck können sich beliebige Objekte, die in diesem Fall als Observer bzw. Beobachter fungieren, bei einem anderen Objekt registrieren. Letzteres Objekt, das man in diesem Fall auch als Subjekt bezeichnet, informiert die registrierten Beobachter, sobald es sich verändert bzw. angepasst wird.

Das Observer Pattern zählt, wie eingangs bereits erwähnt, zu den 1994 in „Design Patterns: Elements of Reusable Object-Oriented Software“ veröffentlichten GoF-Mustern. Die über 20 beschriebenen Musterlösungen für das Softwaredesign spielen bis heute eine wichtige Rolle in der Konzeptionierung und Ausarbeitung von Computeranwendungen.

Zweck und Funktionsweise des Observer Patterns

Das Beobachter-Entwurfsmuster arbeitet mit zwei Typen von Akteuren: Auf der einen Seite steht das Subjekt, also das Objekt, dessen Status langfristig unter Beobachtung stehen soll. Auf der anderen Seite stehen die beobachtenden Objekte (Observer bzw. Beobachter), die über sämtliche Änderungen des Subjekts in Kenntnis gesetzt werden wollen.

Fakt

Typischerweise werden einem Subjekt gleich mehrere Observer zugeordnet. Prinzipiell kann das Observer Pattern aber auch nur für ein einziges beobachtendes Objekt angewendet werden.

Ohne den Einsatz des Beobachter-Patterns müssten die beobachtenden Objekte das Subjekt in regelmäßigen Intervallen um Status-Updates bitten – jede einzelne Anfrage wäre mit entsprechender Rechenzeit inklusive der hierfür erforderlichen Hardware-Ressourcen verbunden. Die grundlegende Idee des Observer Patterns ist es, die Aufgabe des Informierens im Subjekt zu zentralisieren. Zu diesem Zweck führt es eine Liste, in die sich die Beobachter eintragen können. Bei einer Änderung informiert das Subjekt die registrierten Observer der Reihe nach, ohne dass diese selbst aktiv werden müssen. Ist ein automatisches Status-Update nicht mehr für ein bestimmtes, beobachtendes Objekt gewünscht, trägt man es einfach wieder aus der Liste aus.

Hinweis

Zur Information der einzelnen Beobachter sind zwei verschiedene Methoden möglich: Bei der Push-Methode übermittelt das Subjekt den geänderten Zustand bereits mit der Benachrichtigung. Das kann jedoch zu Problemen führen, wenn Informationen übermittelt werden, die der Observer nicht verwerten kann. Dieses Problem besteht bei der alternativen Pull-Methode nicht: Bei diesem Ansatz gibt das Subjekt lediglich die Information weiter, dass Änderungen vorgenommen wurden. Die Beobachter müssen den geänderten Zustand anschließend per gesondertem Methodenaufruf erfragen.

Grafische Darstellung des Observer Patterns (UML-Diagramm)

Die Funktionsweise und der Nutzen von Design Patterns wie dem Observer Pattern sind für Außenstehende häufig nur schwer zu verstehen. Für ein leichteres Verständnis bietet sich daher eine grafische Darstellung der Entwurfsmuster an. Insbesondere die weit verbreitete Modellierungssprache UML (Unified Modeling Language) eignet sich hierfür, da sie die Zusammenhänge gleichermaßen für Anwender und Anwendungsexperten anschaulich und greifbar macht. Aus diesem Grund haben wir auch für die nachfolgende, abstrakte Darstellung des Observer Patterns auf UML als Darstellungssprache zurückgegriffen.

Welche Vor- und Nachteile hat das Observer Design Pattern?

Das Observer Pattern in der Software-Entwicklung einzusetzen, kann sich in vielen Situationen auszahlen. Der große Vorteil, den das Konzept bietet, ist dabei der hohe Unabhängigkeitsgrad zwischen einem beobachteten Objekt (Subjekt) und den beobachtenden Objekten, die sich an dem aktuellen Zustand dieses Objekts orientieren. Das beobachtete Objekt muss beispielsweise keinerlei Informationen über seine Beobachter besitzen, da die Interaktion unabhängig über die Observer-Schnittstelle erledigt wird. Die beobachtenden Objekte erhalten die Updates derweil automatisch, wodurch ergebnislose Anfragen im Observer-Pattern-System (weil sich das Subjekt nicht geändert hat) gänzlich wegfallen.

Dass das Subjekt alle registrierten Beobachter automatisch über jegliche Änderungen informiert, ist jedoch nicht immer von Vorteil: Die Änderungsinformationen werden nämlich auch dann übermittelt, wenn sie für einen der Observer irrelevant sein sollten. Das wirkt sich insbesondere dann negativ aus, wenn die Zahl an registrierten Beobachtern sehr groß ist, da an dieser Stelle durch das Observer-Schema eine Menge Rechenzeit verschenkt werden kann. Ein weiteres Problem des Beobachter-Entwurfsmusters: Häufig ist im Quellcode des Subjekts nicht ersichtlich, welche Beobachter mit Informationen versorgt werden.

Observer Pattern: Wo wird es eingesetzt?

Das Observer Design Pattern ist insbesondere in Anwendungen gefragt, die auf Komponenten basieren, deren Status

  • einerseits stark von anderen Komponenten beobachtet wird,
  • andererseits regelmäßigen Änderungen unterliegt.

Zu den typischen Anwendungsfällen zählen GUIs (Graphical User Interfaces), die Nutzern als einfach zu bedienende Schnittstelle für die Kommunikation mit einer Software dienen. Sobald sie Daten verändern, müssen diese in allen GUI-Komponenten aktualisiert werden – ein Szenario, das optimal durch die Subjekt-Beobachter-Struktur des Observer Patterns abgedeckt wird. Auch Programme, die mit zu visualisierenden Datensätzen (ob klassischen Tabellen oder grafischen Diagrammen) arbeiten, profitieren von der Ordnung durch das Entwurfsmuster.

Hinsichtlich der verwendeten Programmiersprache gibt es prinzipiell keine spezifischen Einschränkungen für das Observer Design Pattern. Wichtig ist lediglich, dass das objektorientierte Paradigma unterstützt wird, damit eine Implementierung des Musters auch sinnvoll ist. Sprachen, in denen der Einsatz des Patterns sehr beliebt ist, sind u. a. C#, C++, Java, JavaScript, Python und PHP.

Observer Pattern: Beispiel für den Einsatz des Beobachter-Entwurfsmusters

Wie genau das Observer Design Pattern in den verschiedenen Programmiersprachen implementiert wird, unterscheidet sich teilweise sehr stark. Der Grundgedanke bleibt jedoch immer der gleiche: Ein bestimmtes Objekt bzw. dessen Zustand wird für eine Vielzahl anderer Objekte leichter zugänglich gemacht. Ein praxisnahes Beispiel liefert das Observer-Pattern-Tutorial auf javabeginners.de, an dem wir uns an dieser Stelle orientieren möchten.

In dem Beispiel soll ein vom „Erzähler“ veröffentlichter Text in den Textfeldern mehrerer „Zuhörer“ angezeigt werden. Die Klasse Erzaehler (das Subjekt) erweitert zu diesem Zweck die Klasse Observable um die Methode addObserver(). Dies ermöglicht das Hinzufügen von Zuhoerern (die Observer). Zudem wird die Methode setChanged() eingeführt, die Veränderungen am Subjekt registriert und im Falle von Neuerungen notifyObservers() aufruft, um alle Beobachter zu informieren.

class Erzaehler extends Observable {
	public Erzaehler(){
		this.addObserver(new Zuhoerer_1());
		this.addObserver(new Zuhoerer_2());
		tell("Text");
	}
	public void tell(String info){
		if(countObservers()>0){
			setChanged();
			notifyObservers(info);
		}
	}
}

Die Observer benötigen zudem eine Implementierung der Beobachter-Schnittstelle inklusive der Methode udpate() und zwei Argumenten: dem beobachteten Objekt und der Änderung in Form einer Objekt-Instanz (das ConcreteSubjekt).

class Zuhoerer extends JFrame implements Observer{
	private JTextField field;
	public Zuhoerer(){
		field1 = new JTextField("a");
		add(field);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(300, 50);
		setVisible(true);
	}
	public void update(Observable o, Object arg) {
		field.setText((String) arg);
	}
}