1. Einführung in das Entwurfsmuster „Beobachter“
Das Muster „Beobachter“ (Observer) ist eines der bekanntesten und grundlegendsten Entwurfsmuster. Es beschreibt eine Situation, in der ein Objekt (das beobachtete Objekt, oder Subject) seine Änderungen anderen Objekten (den Beobachtern, Observers) mitteilt, die sich auf diese Änderungen abonniert haben.
Einfacher gesagt: Wir haben einen Telegram‑Kanal (das beobachtete Objekt) und „Abonnenten“ (Beobachter). Jedes Mal, wenn ein neuer Beitrag erscheint, benachrichtigt der Kanal alle Abonnenten, und diese entscheiden dann, was sie damit tun – lesen, ignorieren oder sich abmelden.
In der Programmierung ermöglicht dieses Muster, interessierte Objekte automatisch über Ereignisse oder Zustandsänderungen zu informieren, ohne sie direkt miteinander zu koppeln. Das ist wichtig, um flexible, erweiterbare und leicht wartbare Systeme zu bauen.
Wo begegnet uns das Muster „Beobachter“?
- In grafischen Oberflächen (Swing, AWT, JavaFX) – Ereignislistener.
- In reaktiven Bibliotheken (RxJava, Project Reactor).
- In der Business‑Logik: Reagieren auf Zustandsänderungen im Modell.
- In Spiele‑Engines (Ereignisse wie Kollision, Sieg, Niederlage usw.).
- Überall dort, wo „was passiert ist“ von „was damit zu tun ist“ getrennt werden soll.
Bezug des Musters zu Ereignissen und Listenern in Java
Tatsächlich basiert das gesamte Ereignismodell von Java auf dem „Beobachter“. Wenn Sie schreiben button.addActionListener(listener);, setzen Sie dieses Muster um:
- Beobachtetes Objekt – der Button (oder eine andere Komponente).
- Beobachter – Ihr Listener, der die Methode actionPerformed() implementiert.
- Ereignis – der Benutzer klickt, bewegt die Maus etc.
- Benachrichtigung – die Komponente ruft actionPerformed() auf.
Das ist eine klassische Umsetzung des Observer!
2. Klassische Implementierung des Musters „Beobachter“
Schauen wir uns an, wie man das Muster mit eigenen Klassen implementiert – ohne Swing und AWT, um zu sehen, dass keine Magie dahintersteckt.
Zentrale Bausteine des Musters
- Observable (Subject) – das beobachtete Objekt. Hält eine Liste von Beobachtern und benachrichtigt sie über Änderungen.
- Observer – das Beobachter‑Interface, üblicherweise mit der Methode update().
Beispiel: Thermometer und Klimaanlage
Beobachter‑Interface
public interface TemperatureObserver {
void temperatureChanged(int newTemperature);
}
Klasse „Thermometer“ (beobachtet)
import java.util.*;
public class Thermometer {
private int temperature;
private final List<TemperatureObserver> observers = new ArrayList<>();
public void addObserver(TemperatureObserver observer) {
observers.add(observer);
}
public void removeObserver(TemperatureObserver observer) {
observers.remove(observer);
}
public void setTemperature(int newTemperature) {
if (this.temperature != newTemperature) {
this.temperature = newTemperature;
notifyObservers();
}
}
private void notifyObservers() {
for (TemperatureObserver observer : observers) {
observer.temperatureChanged(temperature);
}
}
}
Beispiel eines Beobachters – „Klimaanlage“
public class AirConditioner implements TemperatureObserver {
@Override
public void temperatureChanged(int newTemperature) {
if (newTemperature > 25) {
System.out.println("Klimaanlage eingeschaltet! Heiß: " + newTemperature + "°C");
} else {
System.out.println("Klimaanlage ausgeschaltet. Temperatur: " + newTemperature + "°C");
}
}
}
Verwendung
public class Main {
public static void main(String[] args) {
Thermometer thermometer = new Thermometer();
AirConditioner conditioner = new AirConditioner();
thermometer.addObserver(conditioner);
thermometer.setTemperature(22); // Klimaanlage ausgeschaltet. Temperatur: 22°C
thermometer.setTemperature(28); // Klimaanlage eingeschaltet! Heiß: 28°C
}
}
Das ist schon die ganze Magie! Sie können beliebig viele weitere Beobachter hinzufügen – sie alle erhalten Benachrichtigungen, wenn sich die Temperatur ändert.
Grafisches Schema des Musters
flowchart LR
T["Thermometer (Observable)"] -- benachrichtigt --> AC["Klimaanlage (Observer)"]
T -- benachrichtigt --> L["Logger (Observer)"]
T -- benachrichtigt --> Alarm["Alarmanlage (Observer)"]
Moderne Details: veraltetes Observable und neue Ansätze
In der Standardbibliothek von Java gab es java.util.Observable und java.util.Observer, aber seit Java 9 sind sie als veraltet (deprecated) markiert. Der Grund ist mangelnde Flexibilität (zum Beispiel ist Observable eine Klasse und kein Interface, was Mehrfachvererbung an anderer Stelle erschwert).
Ein moderner Ansatz besteht darin, eigene Listener‑Interfaces und die Logik für An-/Abmeldung zu entwerfen (wie im obigen Beispiel). Das ist flexibler, sicherer und passt besser zu realen Anforderungen.
3. Beispiel: Mini‑Anwendung mit Abonnenten
Wir bauen einen „Klick‑Zähler“ mit der Möglichkeit, sich auf Wertänderungen zu abonnieren.
Listener‑Interface
public interface CounterListener {
void counterChanged(int newValue);
}
Zähler‑Klasse
import java.util.*;
public class Counter {
private int value = 0;
private final List<CounterListener> listeners = new ArrayList<>();
public void addCounterListener(CounterListener l) {
listeners.add(l);
}
public void removeCounterListener(CounterListener l) {
listeners.remove(l);
}
public void increment() {
value++;
notifyListeners();
}
private void notifyListeners() {
for (CounterListener l : listeners) {
l.counterChanged(value);
}
}
public int getValue() {
return value;
}
}
Listener: Nachricht ausgeben
public class ConsoleCounterListener implements CounterListener {
@Override
public void counterChanged(int newValue) {
System.out.println("Zähler geändert: " + newValue);
}
}
Verwendung
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
counter.addCounterListener(new ConsoleCounterListener());
counter.increment(); // Zähler geändert: 1
counter.increment(); // Zähler geändert: 2
}
}
4. Nützliche Feinheiten
Moderne Alternativen und Erweiterungen
In realen Projekten nutzt man oft anonyme Klassen oder Lambda‑Ausdrücke für die Anmeldung: counter.addCounterListener(newValue -> System.out.println("Neuer Wert: " + newValue));
(Damit das funktioniert, muss das Interface funktional sein – es darf genau eine abstrakte Methode haben.)
Ebenfalls beliebt sind reaktive Bibliotheken (RxJava, Project Reactor), in denen der „Beobachter“ mit Unterstützung für Ereignisströme, Filterung, Asynchronität usw. umgesetzt ist. Zum Verständnis des Prinzips genügt das oben betrachtete klassische Schema.
Einsatz des Musters „Beobachter“ im Alltag
- Datenmodelle. Eine Änderung im Modell (Aufgabenliste, Waren, Benutzer) benachrichtigt die Views zur Aktualisierung.
- Logging. Ein abonnierter Logger reagiert auf Ereignisse im gesamten System.
- Benachrichtigungen. Bei Zustandsänderungen – Versand von E‑Mail, Push‑Benachrichtigungen, Nachrichten in Telegram.
- Spiele. Änderung der Gesundheit, Erscheinen eines Gegners, Abschluss eines Levels.
- Multithreading. Ein Thread veröffentlicht Ereignisse, andere reagieren.
5. Typische Fehler bei der Implementierung des Musters „Beobachter“
Fehler Nr. 1: Listener nicht entfernt. Wenn ein Listener nicht mehr benötigt wird, aber nicht entfernt wurde, erhält er weiterhin Benachrichtigungen. In langlebigen Anwendungen kann das zu Memory Leaks führen.
Fehler Nr. 2: Langsame oder blockierende Operationen in Handlern. Wenn ein Handler schwere Arbeit verrichtet (IO, DB), kann die Anwendung „hängen bleiben“, besonders wenn Benachrichtigungen aus dem UI‑Thread kommen. Verschieben Sie schwere Aufgaben in Hintergrund‑Threads.
Fehler Nr. 3: Exceptions in Listenern. Eine Exception in einem Listener kann die Zustellung an die übrigen unterbrechen. Umbrechen Sie die Aufrufe der Listener in try-catch und loggen Sie Fehler.
Fehler Nr. 4: Mehrfache Registrierung desselben Listeners. Wenn derselbe Listener mehrfach hinzugefügt wird, erhält er das Ereignis entsprechend oft. Achten Sie auf die Registrierung und schützen Sie sich vor doppeltem Hinzufügen.
Fehler Nr. 5: Starke Kopplung zwischen beobachtetem Objekt und Beobachter. Wenn das beobachtete Objekt konkrete Implementierungen der Beobachter kennt, geht die geringe Kopplung verloren. Verwenden Sie nur Interfaces (zum Beispiel TemperatureObserver, CounterListener).
GO TO FULL VERSION