Entwurfsmuster Beobachter (Observer)
https://bildung.social/@oerinformatik/
https://oer-informatik.de/entwurfsmuster-observer
Entwurfsmuster, Beobachter-Muster, Observer, Verhaltensmuster, UML-Klassendiagramm, UML-Sequenzdiagramm, Push- und Pull-Modell, MVC, Datenbindung, lose Kopplung
tl/dr; (ca. 26 min Lesezeit): Das Beobachter-Muster (Observer) sorgt dafür, dass viele Objekte automatisch erfahren, wenn sich ein anderes Objekt ändert – etwa alle Anzeigen, wenn neue Messwerte eintreffen. Der Artikel erklärt Problem und Rollen, das Klassendiagramm mit Realisierung und Aggregation, die Methoden
addObserver,notifyObserversundupdatein Pseudocode und Java sowie den Ablauf im Sequenzdiagramm. Dazu kommen die Variante mit abstrakter Klasse, Push- und Pull-Modell, Namensvarianten wie Listener, der Zusammenhang mit MVC und Datenbindung sowie typische Fehler in Aufgaben [AI-Assisted]. (Zuletzt geändert am 28.10.2026)
Dieser Artikel ist Teil einer Artikelreihe zur objektorientierten Modellierung: Klassendiagramm / Abstrakte Klassen und Interfaces / Polymorphie / Entwurfsmuster: Strategie, Zustand, Proxy und Dekorierer / Beobachter (Observer) / Übungsaufgaben zum Beobachter-Muster
Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.
Dieser Artikel baut auf der Kann-Liste der Artikel Entwurfsmuster: Strategie, Zustand, Proxy und Dekorierer und UML-Sequenzdiagramme auf.
Als Grundlagen zum Beobachter-Muster kann ich nach Durchlesen des Artikels…
… das Problem beschreiben, das das Beobachter-Muster löst, es den Verhaltensmustern zuordnen und für eine beschriebene Situation begründen, warum es passt.
… die Rollen Subjekt, konkretes Subjekt, Observer und konkreter Observer in einem Klassendiagramm erkennen und zuordnen.
… ein vorgegebenes Klassendiagramm nach dem Beobachter-Muster um die fehlenden Methoden (
addObserver,removeObserver,notifyObservers,update) und die Beziehungen ergänzen.… die Beziehungen im Muster erläutern: Realisierung der Interfaces, Aggregation zwischen Subjekt und Observern, Generalisierung bei einer abstrakten Subjekt-Klasse – und begründen, warum sie sinnvoll sind (Polymorphie, lose Kopplung).
… die Methode
notifyObserverssowie den Konstruktor eines Observers, der sich mitaddObserver(this)anmeldet, in Pseudocode angeben.… eine Methode
updateformulieren, die sich die neuen Daten über Getter vom Subjekt holt und anzeigt.… den Ablauf von der Registrierung über die Datenänderung bis zur Anzeige in einem UML-Sequenzdiagramm darstellen.
… die Benachrichtigungsreihenfolge im MVC-Muster angeben und den Begriff Datenbindung (data binding) erläutern.
Darüber hinaus kann ich als Expertenwissen…
… Push- und Pull-Modell unterscheiden und je nach Situation (z. B. Datensparsamkeit) eines begründet auswählen.
… Namensvarianten wie Listener, Observable oder Beobachter wiedererkennen und den Beobachter von Publish-Subscribe mit Broker (z. B. MQTT) abgrenzen.
… typische Fehler (Benachrichtigung vor der Datenänderung, vergessenes Abmelden, Aufruf am Interface statt am Element) erkennen und korrigieren.
Stell Dir eine Sporthalle bei einem Handballspiel vor. Fällt ein Tor, muss sich die große Anzeigetafel ändern, der Liveticker im Internet soll einen Eintrag posten, und die Statistik-Software zählt mit. Wer soll all diese Stellen informieren? Die Klasse, die den Spielstand verwaltet, könnte jede Anzeige direkt aufrufen – dann müsste sie aber jede Anzeige kennen und bei jeder neuen Anzeige geändert werden. Genau dieses Problem löst das Beobachter-Muster.
Problem und Lösungsidee
Problem: Ein Objekt ändert seinen Zustand, und eine wechselnde Zahl anderer Objekte muss darauf reagieren. Das Objekt soll diese anderen Objekte aber nicht im Einzelnen kennen – sonst wäre es eng an sie gekoppelt, und jede neue Anzeige würde eine Änderung am Objekt erfordern.
Lösungsidee: Die interessierten Objekte – die Beobachter (observer) – melden sich beim beobachteten Objekt, dem Subjekt (subject), an. Das Subjekt führt eine Liste dieser Beobachter. Ändern sich seine Daten, geht es die Liste durch und ruft bei jedem Beobachter dieselbe Methode update() auf. Was der einzelne Beobachter daraus macht, entscheidet er selbst.
Die „Gang of Four“ (GoF), die das Muster 1994 beschrieben haben, fassen den Zweck so zusammen: Das Muster definiert eine 1-zu-n-Abhängigkeit zwischen Objekten, sodass bei einer Zustandsänderung eines Objekts alle abhängigen Objekte automatisch benachrichtigt und aktualisiert werden. Weitere Namen sind Publish-Subscribe und Dependents; im Deutschen heißt das Muster auch Beobachter.
Der Beobachter gehört zu den Verhaltensmustern (behavioral patterns): Er regelt, wie Objekte zusammenarbeiten und wer wen wann informiert. Weitere Verhaltensmuster sind Strategie, Zustand, Schablonenmethode, Befehl (Command) und Iterator.
Typische Einsatzfälle:
Mehrere Anzeigen sollen aktuelle Daten zeigen (Messwerte, Füllstände, Auslastung, Spielstand, Börsenkurse).
Eine Benutzeroberfläche soll sich aktualisieren, wenn sich die Daten im Hintergrund ändern (Model und View im MVC-Muster).
Auf Ereignisse wie Klicks oder Tastendrücke sollen beliebig viele Programmteile reagieren (Event-Listener in GUI-Bibliotheken).
Ein Statuswechsel (Bestellung versandt, Termin abgesagt) soll Benachrichtigungen per App, E-Mail oder Protokoll auslösen.
Die Rollen im Klassendiagramm
In Aufgaben ist das Muster meist mit zwei Interfaces modelliert – eines für das Subjekt, eines für die Beobachter. Im Beispiel verwaltet die Klasse Spielstand die Tore; Anzeigetafel und Liveticker sind Beobachter:
| Rolle | im Beispiel | Aufgabe |
|---|---|---|
Subjekt (Subject, in Aufgaben oft ObservedSubject) |
Interface Subject |
legt fest, dass man Beobachter an- und abmelden und benachrichtigen kann: addObserver, removeObserver, notifyObservers |
| Konkretes Subjekt (ConcreteSubject) | Spielstand |
hält die Daten und die Liste observers, implementiert die drei Methoden, ruft nach jeder Datenänderung notifyObservers() auf und bietet Getter für die Daten |
| Beobachter (Observer) | Interface Observer |
legt die Methode fest, die bei einer Änderung aufgerufen wird: update() |
| Konkreter Beobachter (ConcreteObserver) | Anzeigetafel, Liveticker |
implementiert update() auf seine eigene Weise, meist mit einer Referenz auf das Subjekt, um sich die neuen Daten zu holen |
Wichtig beim Ergänzen von Klassendiagrammen: Eine Klasse, die ein Interface realisiert, muss alle Methoden des Interfaces implementieren. Im Klassendiagramm werden diese Methoden in der realisierenden Klasse noch einmal notiert – Spielstand enthält also addObserver, removeObserver und notifyObservers, Anzeigetafel und Liveticker enthalten jeweils update(). Fehlen sie, ist das Diagramm unvollständig. Daneben haben die Klassen ihre eigenen Methoden wie setTore(…), zeige(…) oder poste(…).
Die Beziehungen erläutern
In fast jeder Aufgabe zum Beobachter sollst Du eine Beziehung einzeichnen und ihre Art erläutern oder begründen. Es gibt drei Arten von Beziehungen.
Realisierung: Spielstand und Subject, Anzeigetafel und Observer
Gestrichelte Linie mit hohlem Dreieck am Interface. Es handelt sich um eine Realisierung (Schnittstellenimplementierung): Ein Interface ist eine Vorlage, von der selbst keine Objekte erzeugt werden können. Die realisierende Klasse verpflichtet sich, alle abstrakten Methoden des Interfaces zu implementieren. Von ihr können dann Objekte erzeugt werden, die überall eingesetzt werden dürfen, wo der Interface-Typ erwartet wird.
Warum ist das sinnvoll? Anzeigetafel übernimmt durch die Realisierung die Rolle eines Observers. Das Subjekt ruft in notifyObservers() bei allen Objekten in seiner Liste update() auf – es weiß nur, dass jedes dieser Objekte ein Observer ist. Welche Implementierung von update() ausgeführt wird, entscheidet sich erst zur Laufzeit anhand des tatsächlichen Objekts (Polymorphie, späte Bindung): Die Tafel zeigt Zahlen an, der Ticker postet einen Text. Neue Beobachter (eine Statistik-App, ein Hallensprecher-Bildschirm …) lassen sich ergänzen, ohne Spielstand zu ändern (lose Kopplung, loose coupling; Offen-geschlossen-Prinzip, open-closed principle).
Aggregation: Spielstand und Observer
Durchgezogene Linie mit hohler Raute am Subjekt. Das Subjekt „hat“ beliebig viele Beobachter (has-a-Beziehung, Multiplizität 0..* beim Observer). Es ist eine Aggregation und keine Komposition, weil die Beobachter unabhängig vom Subjekt existieren: Sie werden außerhalb erzeugt, melden sich an und wieder ab und können gleichzeitig mehrere Subjekte beobachten. Endet das Spiel, bleibt die Anzeigetafel bestehen. Manchmal sieht man hier auch eine Komposition; fachlich sauberer ist die Aggregation. Multiplizitäten sind optional, machen das Diagramm aber genauer.
Die Beziehung führt zum Interface Observer, nicht zu den konkreten Klassen. Genau das macht das Muster erweiterbar: Das Subjekt kennt keine Anzeigetafel, nur „irgendeinen Observer“.
Referenz vom Beobachter auf das Subjekt
Damit sich ein Beobachter die neuen Daten holen kann, braucht er eine Referenz auf das Subjekt – im Beispiel das Attribut -spielstand: Spielstand, das im Konstruktor gesetzt wird. In Diagrammen erscheint diese Beziehung oft nur als Attribut; als Linie wäre es eine gerichtete Assoziation vom Beobachter zum Subjekt.
Achtung, Typfalle: In manchen Aufgaben ist dieses Attribut mit dem Interface-Typ angegeben (z. B. - daten : ObservedSubject), update() ruft aber einen Getter wie getDurchschnitt() auf, den es nur im konkreten Subjekt gibt. In einer streng typisierten Sprache wie Java ginge das nur mit einer Typumwandlung. Wenn Du selbst modellierst, gib der Referenz den Typ des konkreten Subjekts – oder nimm die Getter mit in das Subjekt-Interface auf.
Die Methoden in Pseudocode und Java
Die folgenden Methoden werden in Aufgaben immer wieder verlangt. Pseudocode muss keiner bestimmten Sprache folgen, aber Objekte, Methodenaufrufe und Schleifen müssen erkennbar sein. Die Beispiele kannst Du in vier Notationen anzeigen lassen; die Notationen sind im Artikel Algorithmen mit Pseudocode dokumentieren beschrieben.
An- und Abmelden im Subjekt:
Prozedur addObserver(o: Observer)
observers.hinzufuegen(o)
Ende addObserver
Prozedur removeObserver(o: Observer)
observers.entfernen(o)
Ende removeObserver
Benachrichtigen: Die Kernmethode des Musters. Sie läuft über alle registrierten Beobachter und ruft bei jedem update() auf:
Prozedur notifyObservers()
für jedes o in observers
o.update()
Ende für
Ende notifyObservers
Mit einer Zählschleife (Index beginnt bei 0) geht es genauso:
Prozedur notifyObservers()
zähle i von 0 bis laenge(observers) - 1
observers[i].update()
Ende zähle
Ende notifyObservers
Vollständig ist die Methode mit drei Dingen: den Methodenkopf, die Schleife über alle Beobachter und den Aufruf von update() am jeweiligen Element (nicht am Interface Observer).
Daten ändern und benachrichtigen: Die set-Methode des Subjekts ändert zuerst die Daten und ruft danach notifyObservers() auf – sonst holen sich die Beobachter noch die alten Werte.
Prozedur setTore(heim: integer, gast: integer)
toreHeim := heim
toreGast := gast
notifyObservers()
Ende setTore
Konstruktor eines Beobachters: Er speichert die Referenz auf das Subjekt und meldet sich gleich an. Dabei übergibt er sich selbst mit this (in Pseudo-Python self):
Konstruktor Anzeigetafel(s: Spielstand)
spielstand := s
s.addObserver(this)
Ende Anzeigetafel
update im Beobachter: Der Beobachter holt sich über Getter die Daten, die er braucht, und ruft seine Anzeigemethode auf:
Prozedur update()
heim: integer := spielstand.getToreHeim()
gast: integer := spielstand.getToreGast()
zeige(heim, gast)
Ende update
In Java sieht das Ganze so aus:
public interface Observer {
void update();
}
public interface Subject {
void addObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
public class Spielstand implements Subject {
private final List<Observer> observers = new ArrayList<>();
private int toreHeim;
private int toreGast;
public void addObserver(Observer o) { observers.add(o); }
public void removeObserver(Observer o) { observers.remove(o); }
public void notifyObservers() {
for (Observer o : observers) {
o.update();
}
}
public void setTore(int heim, int gast) {
this.toreHeim = heim;
this.toreGast = gast;
notifyObservers(); // erst ändern, dann benachrichtigen
}
public int getToreHeim() { return toreHeim; }
public int getToreGast() { return toreGast; }
}
public class Anzeigetafel implements Observer {
private final Spielstand spielstand;
public Anzeigetafel(Spielstand s) {
this.spielstand = s;
s.addObserver(this); // Anmeldung im Konstruktor
}
public void update() {
zeige(spielstand.getToreHeim(), spielstand.getToreGast());
}
private void zeige(int heim, int gast) {
System.out.println("HEIM " + heim + " : " + gast + " GAST");
}
}Ein kurzes Hauptprogramm zeigt die Wirkung:
Spielstand stand = new Spielstand();
Anzeigetafel tafel = new Anzeigetafel(stand); // meldet sich selbst an
stand.setTore(12, 9); // Ausgabe: HEIM 12 : 9 GASTDer Ablauf im Sequenzdiagramm
Wie die Objekte zur Laufzeit zusammenspielen, zeigt ein Sequenzdiagramm. Aufgaben geben dazu oft eine Ablaufbeschreibung vor, nach der Du ein angefangenes Diagramm ergänzt:
Der Client erzeugt ein Subjekt und einen Beobachter; dem Beobachter wird das Subjekt übergeben.
Im Konstruktor des Beobachters wird
addObserver(…)am Subjekt aufgerufen.Der Client ruft die set-Methode des Subjekts auf.
Die set-Methode startet
notifyObservers()– ein Selbstaufruf mit eigenem, versetztem Aktivierungsbalken.notifyObservers()ruftupdate()bei jedem Beobachter auf (Schleife).update()holt sich mit Gettern die Daten vom Subjekt (synchrone Nachricht mit Antwort und Rückgabewert) und ruftzeige(…)als Selbstaufruf auf.Der Kontrollfluss läuft in umgekehrter Reihenfolge zurück: von
zeigeüberupdate,notifyObserversund die set-Methode zum Client.
Worauf Du beim Zeichnen achten musst:
Erzeugte Objekte beginnen mit ihrem Kopf auf Höhe der Erzeugungsnachricht (gestrichelter Pfeil,
new(…)bzw.create).Die Anmeldung geht vom Beobachter zum Subjekt – noch während der Konstruktor läuft. Erst danach kehrt die Erzeugung zum Client zurück.
Synchrone Nachrichten haben eine gefüllte Pfeilspitze, Antworten sind gestrichelt mit offener Spitze. Getter-Antworten werden mit dem Rückgabewert beschriftet (z. B.
getToreHeim(): 12).notifyObservers()undzeige(…)sind Selbstaufrufe: Pfeil zur eigenen Lebenslinie, darauf ein geschachtelter Balken.Der Rückruf des Beobachters (
getToreHeim()) trifft das Subjekt, während es noch innotifyObservers()steckt – auch dafür entsteht ein weiterer kurzer, versetzter Balken.Die Schleife über alle Beobachter darfst Du mit einem
loop-Fragment zeigen; Aufgaben mit nur einem Beobachter verlangen sie meist nicht.
Variante: Subjekt als abstrakte Klasse
Statt eines Subjekt-Interfaces kann eine abstrakte Klasse die gemeinsame Logik bereitstellen: die Liste der Beobachter und die fertig implementierten Methoden zum An- und Abmelden und Benachrichtigen. Konkrete Subjekte erben davon und müssen den Code nicht wiederholen.
Die Beziehung
Spielstand–Subjektist nun eine Generalisierung (Vererbung): durchgezogene Linie, hohles Dreieck an der Oberklasse.Spielstand„ist ein“ Subjekt und erbt Attribut und Methoden.notifyObservers()ist oft geschützt (#, protected): Nur das Subjekt selbst und seine Unterklassen sollen benachrichtigen – nämlich genau dann, wenn sich ihre Daten geändert haben. Von außen soll niemand grundlos Benachrichtigungen auslösen.Häufig gibt die abstrakte Klasse eine abstrakte set-Methode vor (z. B.
setData(…) {abstract}). Jedes konkrete Subjekt muss sie überschreiben; im Klassendiagramm steht sie dann in der Unterklasse noch einmal. Die abstrakte Methode wird kursiv oder mit{abstract}gekennzeichnet.Die Beobachter bleiben über ein Interface angebunden: Eine Klasse kann in Java nur von einer Klasse erben, aber beliebig viele Interfaces realisieren. Eine Anzeige, die schon von einer GUI-Klasse erbt, kann so trotzdem Beobachter sein.
Nachteil der Variante: Ein Subjekt, das bereits eine andere Oberklasse hat, kann nicht zusätzlich von Subjekt erben. Die Interface-Variante ist flexibler, die abstrakte Klasse spart doppelten Code.
Push- oder Pull-Modell?
Bisher hat sich der Beobachter die Daten in update() selbst geholt (Pull-Modell). Alternativ kann das Subjekt die geänderten Daten gleich mitschicken (Push-Modell):
| Modell | Methode | Vorteile | Nachteile |
|---|---|---|---|
| Pull | update() – der Beobachter ruft danach Getter des Subjekts auf |
jeder Beobachter holt nur, was er braucht; die Schnittstelle bleibt stabil, wenn Daten dazukommen | Beobachter braucht eine Referenz auf das Subjekt; mehrere Rückrufe; muss herausfinden, was sich geändert hat |
| Push | update(heim, gast) oder update(ereignis) – die Daten kommen als Parameter |
keine Rückrufe, Beobachter muss das Subjekt nicht kennen | alle Beobachter bekommen dieselben (evtl. zu vielen) Daten; ändern sich die Daten, ändert sich die Schnittstelle aller Beobachter |
Eine häufige Mischform übergibt in update(quelle) die Referenz auf das Subjekt selbst. Dann kann ein Beobachter mehrere Subjekte beobachten und erkennt, welches sich geändert hat. Beim Push-Modell lohnt ein Blick auf den Datenschutz: Werden personenbezogene Daten an alle Beobachter geschoben, bekommt sie auch, wer sie gar nicht braucht. Das widerspricht dem Grundsatz der Datenminimierung (Art. 5 Abs. 1 lit. c DSGVO); ein schmaler Push mit nur den nötigen Angaben oder das Pull-Modell sind dann besser.
Viele Namen für dasselbe Muster
In Aufgaben, Frameworks und Fachbüchern heißen die Rollen und Methoden unterschiedlich. Lass Dich davon nicht verwirren – entscheidend ist die Struktur:
| Rolle bzw. Methode | übliche Namen |
|---|---|
| Subjekt | Subject, ObservedSubject, Observable, Model, Publisher, Beobachtbares, Subjekt |
| Beobachter | Observer, Listener (z. B. ChangeListener, ActionListener), Subscriber, Beobachter |
| anmelden | addObserver, attach, register, subscribe, addChangeListener, addEventListener |
| abmelden | removeObserver, detach, unregister, removeChangeListener |
| benachrichtigen | notifyObservers, notify, fireChange, notifyChangeListeners |
| Aktualisierungsmethode | update, changed, propertyChange, actionPerformed, aktualisiere |
In Java gab es lange die fertigen Typen java.util.Observable und java.util.Observer. Sie gelten seit Java 9 als veraltet (deprecated), unter anderem weil Observable eine Klasse ist (man muss davon erben) und die Benachrichtigungsreihenfolge nicht festgelegt war. Heute nutzt man eigene Interfaces wie oben, PropertyChangeListener aus java.beans oder die Listener-Mechanismen der jeweiligen GUI-Bibliothek. In JavaScript ist element.addEventListener("click", …) ebenfalls eine Anmeldung als Beobachter.
Beobachter im MVC-Muster
Das Model-View-Controller-Muster (MVC) trennt eine Anwendung mit Benutzeroberfläche in drei Teile: das Model (Daten und Geschäftslogik), die View (Darstellung) und den Controller (verarbeitet Eingaben). Model und View sind häufig über das Beobachter-Muster verbunden: Das Model ist das Subjekt, die Views sind Beobachter. So kann es zu denselben Daten beliebig viele Ansichten geben – etwa eine Tabelle und ein Diagramm –, ohne dass das Model sie kennt.
Bei einer Benutzereingabe läuft die Benachrichtigung in dieser Reihenfolge:
Die View informiert den Controller über die Benutzereingabe (z. B. Klick auf „Speichern“).
Der Controller fordert das Model zur Zustandsänderung auf (z. B.
setData(…)).Das Model informiert die View(s) über die Zustandsänderung (
notifyObservers()→update()).Die View fordert die geänderten Daten vom Model an (Getter, Pull) und zeigt sie an.
Das Sequenzdiagramm zeigt diesen Ablauf am Beispiel eines Klicks auf „Speichern“:
Ein Merksatz dazu: Eingabe geht über den Controller ins Model, Änderungen kommen über den Beobachter aus dem Model.
Datenbindung (data binding)
Viele moderne Frameworks nehmen Dir diese Verdrahtung ab. Bei der Datenbindung wird eine Eigenschaft eines Datenobjekts mit einem Steuerelement der Oberfläche verknüpft: Ändert sich der Wert im Datenobjekt, zeigt das Steuerelement ihn automatisch an; bei einer Zwei-Wege-Bindung (two-way binding) gelangen Eingaben im Steuerelement auch automatisch zurück ins Datenobjekt. Intern arbeitet Datenbindung meist mit genau dem Beobachter-Prinzip – nur dass Du addObserver, notifyObservers und update nicht selbst schreibst. Beispiele sind Bindings in JavaFX, WPF (C#), Angular oder Vue.
Abgrenzung: Beobachter, Polling und Publish-Subscribe
Polling (regelmäßiges Nachfragen) ist die Alternative ohne Muster: Jede Anzeige fragt z. B. alle 10 Sekunden nach, ob sich etwas geändert hat. Das erzeugt unnötige Last, wenn sich nichts ändert, und Verzögerungen, wenn sich etwas ändert. Der Beobachter benachrichtigt sofort und nur bei Änderungen.
Publish-Subscribe mit Broker: Bei verteilten Systemen schickt ein Sender seine Nachrichten an einen Vermittler (Broker, z. B. einen MQTT-Broker) unter einem Thema (Topic); Empfänger abonnieren Themen beim Broker. Sender und Empfänger kennen sich dann überhaupt nicht mehr und laufen oft auf verschiedenen Rechnern, die Zustellung ist meist asynchron. Beim klassischen Beobachter dagegen kennt das Subjekt seine Beobachter (über das Interface) und ruft sie direkt und synchron im selben Programm auf.
Vorteile, Nachteile und Fallstricke
Vorteile:
Lose Kopplung: Das Subjekt kennt nur das Observer-Interface. Subjekt und Beobachter lassen sich unabhängig voneinander ändern, testen und wiederverwenden.
Erweiterbarkeit: Neue Beobachter kommen hinzu, ohne das Subjekt zu ändern (Offen-geschlossen-Prinzip).
Dynamik: Beobachter können sich zur Laufzeit an- und abmelden.
Konsistenz: Alle Anzeigen zeigen nach einer Änderung denselben Stand.
Nachteile und Fallstricke:
Reihenfolge: Das Muster legt nicht fest, in welcher Reihenfolge die Beobachter benachrichtigt werden. Wer sich darauf verlässt, baut versteckte Abhängigkeiten.
Kaskaden und Endlosschleifen: Ändert ein Beobachter in
update()wieder das Subjekt, löst er eine neue Benachrichtigung aus – im schlimmsten Fall endlos.Vergessenes Abmelden: Solange ein Beobachter in der Liste steht, bleibt er im Speicher und wird weiter aufgerufen, auch wenn ihn niemand mehr braucht (lapsed listener, Speicherleck). Geschlossene Fenster sollten sich deshalb mit
removeObserver(this)abmelden.Abmelden während der Benachrichtigung: Entfernt sich ein Beobachter in
update()aus der Liste, währendnotifyObservers()darüber läuft, kann die Schleife Elemente überspringen oder (in Java) eineConcurrentModificationExceptionwerfen. Abhilfe: über eine Kopie der Liste laufen.Leistung: Viele Beobachter oder sehr häufige Änderungen kosten Zeit, weil jede Änderung alle Beobachter aufruft – unter Umständen auch dann, wenn sie die geänderten Daten gar nicht interessieren.
Der Beobachter in Aufgaben
Aufgaben zum Beobachter-Muster folgen fast immer demselben Aufbau. Diese Schritte helfen Dir beim Bearbeiten:
Muster nennen und begründen: „Ändert sich X, sollen alle Anzeigen aktualisiert werden“ – das ist der Beobachter. Begründung mit 1-zu-n-Abhängigkeit, automatischer Benachrichtigung und loser Kopplung am Beispiel der Aufgabe.
Klassendiagramm ergänzen: Aggregation vom konkreten Subjekt zum Observer-Interface (Raute am Subjekt); in jeder realisierenden Klasse die Interface-Methoden mit vollständiger Signatur nachtragen (
addObserver,notifyObservers… im Subjekt,update()in jedem Beobachter). Klassen mit schon vorhandenen Einträgen nicht übersehen.Beziehungen erläutern: Realisierung = Interface nicht instanziierbar, alle abstrakten Methoden müssen implementiert werden; Aggregation = Subjekt hat mehrere Beobachter, die unabhängig existieren; Begründung mit Rolle, Polymorphie und Erweiterbarkeit.
Pseudocode:
notifyObserversmit Schleife undo.update(); Konstruktor mit Referenz speichern undsubjekt.addObserver(this); set-Methode erst ändern, dann benachrichtigen.Sequenzdiagramm: Ablaufbeschreibung Schritt für Schritt abarbeiten, jede Nachricht mit Antwort, Selbstaufrufe mit geschachtelten Balken, Rückgabewerte beschriften.
Die Namen in der Aufgabe übernimmst Du genau so, wie sie im Klassendiagramm stehen – auch wenn sie Englisch, Deutsch oder gemischt sind.
Typische Fehler:
Observer.update()statto.update()– der Aufruf muss am Element der Liste erfolgen, nicht am Interface.Zählschleife von Index 0 bis zur Anzahl der Beobachter – mit Index ab 0 geht der letzte gültige Index nur bis Anzahl − 1.
notifyObservers()wird vor der Datenänderung aufgerufen; die Beobachter zeigen dann den alten Stand.Raute am Observer-Interface statt am Subjekt, Vererbungspfeil (durchgezogen) statt Realisierung (gestrichelt), Pfeil vom Interface zur Klasse statt umgekehrt.
Im Sequenzdiagramm ruft das Subjekt
getToreHeim()auf – die Getter ruft aber der Beobachter am Subjekt auf.
Zum Üben findest Du zwei umfangreiche Aufgaben in den Übungsaufgaben zum Beobachter-Muster.
Leitfragen
- Welches Problem löst das Beobachter-Muster, zu welcher Kategorie gehört es, und woran erkennst Du in einer Aufgabenstellung, dass es passt?
Das Beobachter-Muster sorgt dafür, dass bei einer Zustandsänderung eines Objekts (Subjekt) eine wechselnde Zahl abhängiger Objekte automatisch benachrichtigt wird, ohne dass das Subjekt sie im Einzelnen kennt (1-zu-n-Abhängigkeit, lose Kopplung). Es gehört zu den Verhaltensmustern. In einer Aufgabe erkennst Du es an Formulierungen wie „Ändert sich X, sollen alle Anzeigen aktualisiert werden“ – siehe Abschnitt „Problem und Lösungsidee“ und Abschnitt „Der Beobachter in Aufgaben“.
- Welche Rollen gibt es im Muster, welche Methoden gehören zu welcher Rolle, und welche davon musst Du in einem unvollständigen Klassendiagramm nachtragen?
Es gibt vier Rollen: Das Subjekt-Interface legt addObserver, removeObserver und notifyObservers fest, das konkrete Subjekt hält die Daten und die Liste observers, implementiert diese Methoden und bietet Getter. Das Observer-Interface legt update() fest, und jeder konkrete Beobachter implementiert update() auf seine eigene Weise. Weil eine realisierende Klasse alle Methoden des Interfaces implementieren muss und diese im Klassendiagramm noch einmal notiert werden, trägst Du im konkreten Subjekt addObserver, removeObserver und notifyObservers und in jedem konkreten Beobachter update() nach – siehe Abschnitt „Die Rollen im Klassendiagramm“.
- Wie erläuterst Du die Beziehung zwischen konkretem Subjekt und Observer-Interface, und warum ist es keine Komposition?
Es ist eine Aggregation (hohle Raute am Subjekt, Multiplizität 0..* beim Observer): Das Subjekt „hat“ beliebig viele Beobachter, und die Beziehung führt zum Interface, nicht zu konkreten Klassen. Keine Komposition, weil die Beobachter unabhängig vom Subjekt existieren – sie werden außerhalb erzeugt, melden sich an und ab und können mehrere Subjekte beobachten – siehe Abschnitt „Aggregation: Spielstand und Observer“.
- Was bedeutet die Realisierung des Observer-Interfaces für eine Anzeigeklasse, und warum ist sie sinnvoll (Polymorphie, lose Kopplung)?
Durch die Realisierung übernimmt die Anzeigeklasse die Rolle eines Observers und muss alle abstrakten Methoden des Interfaces, also update(), implementieren. Das Subjekt ruft update() nur über den Interface-Typ auf; welche Implementierung läuft, entscheidet sich zur Laufzeit (Polymorphie). So lassen sich neue Beobachter ergänzen, ohne das Subjekt zu ändern (lose Kopplung) – siehe Abschnitt „Realisierung: Spielstand und Subject, Anzeigetafel und Observer“.
- Wie sehen
notifyObservers()und der Konstruktor eines Beobachters, der sich selbst anmeldet, in Pseudocode aus?
notifyObservers() läuft mit einer Schleife über alle registrierten Beobachter und ruft am jeweiligen Element o.update() auf (nicht am Interface). Der Konstruktor des Beobachters speichert die Referenz auf das Subjekt (spielstand := s) und meldet sich mit s.addObserver(this) an – siehe Abschnitt „Die Methoden in Pseudocode und Java“.
- Wie läuft das Zusammenspiel von Registrierung, Datenänderung, Benachrichtigung und Anzeige im Sequenzdiagramm ab?
Der Client erzeugt Subjekt und Beobachter, der Beobachter ruft noch im Konstruktor addObserver(…) am Subjekt auf. Dann ruft der Client die set-Methode auf, die per Selbstaufruf notifyObservers() startet; diese ruft update() bei jedem Beobachter auf, der sich mit Gettern die Daten vom Subjekt holt und zeige(…) aufruft. Danach läuft der Kontrollfluss in umgekehrter Reihenfolge zum Client zurück – siehe Abschnitt „Der Ablauf im Sequenzdiagramm“.
- Was ändert sich, wenn das Subjekt eine abstrakte Klasse statt eines Interfaces ist?
Die abstrakte Klasse stellt die Beobachterliste und die fertig implementierten Methoden bereit; konkrete Subjekte erben davon (Generalisierung statt Realisierung) und sparen doppelten Code. notifyObservers() ist oft protected, und häufig wird eine abstrakte set-Methode vorgegeben. Nachteil: Ein Subjekt mit einer anderen Oberklasse kann nicht zusätzlich erben – siehe Abschnitt „Variante: Subjekt als abstrakte Klasse“.
- Worin unterscheiden sich Push- und Pull-Modell, und wann ist welches besser?
Beim Pull-Modell ruft das Subjekt nur update() auf und der Beobachter holt sich per Getter, was er braucht; die Schnittstelle bleibt stabil, er braucht aber eine Referenz auf das Subjekt. Beim Push-Modell kommen die Daten als Parameter mit, ohne Rückrufe, aber alle bekommen dieselben Daten und Datenänderungen ändern die Schnittstelle. Bei personenbezogenen Daten ist wegen der Datenminimierung Pull oder ein schmaler Push besser – siehe Abschnitt „Push- oder Pull-Modell?“.
- In welcher Reihenfolge laufen die Benachrichtigungen im MVC-Muster, und was versteht man unter Datenbindung?
Die View informiert den Controller über die Eingabe, der Controller fordert das Model zur Zustandsänderung auf, das Model informiert die Views (notifyObservers() → update()), und die View holt sich die geänderten Daten vom Model. Bei der Datenbindung wird eine Eigenschaft eines Datenobjekts mit einem Steuerelement verknüpft, sodass Änderungen automatisch angezeigt werden (bei Zwei-Wege-Bindung auch umgekehrt) – siehe Abschnitt „Beobachter im MVC-Muster“ und Abschnitt „Datenbindung (data binding)“.
Links und weitere Informationen
Entwurfsmuster: Strategie, Zustand, Proxy und Dekorierer (Einführung, Kategorien, Vorteile von Mustern)
als Nachschlagewerk: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster – Elemente wiederverwendbarer objektorientierter Software. mitp (deutsche Ausgabe des GoF-Buchs), Kapitel „Beobachter“
zum Stöbern: Refactoring.Guru: Beobachter (Musterbeschreibung mit Beispielen in vielen Sprachen)
zur Java-Praxis: Java-API: java.util.Observable (deprecated seit Java 9)
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Entwurfsmuster Beobachter (Observer)” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/entwurfsmuster-observer veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-pattern/entwurfsmuster. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
