CodeGym /Kurse /JAVA 25 SELF /Entwurfsmuster Observer (Beobachter)

Entwurfsmuster Observer (Beobachter)

JAVA 25 SELF
Level 50 , Lektion 3
Verfügbar

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).

1
Aufgabe
JAVA 25 SELF, Level 50, Lektion 3
Gesperrt
Gründung der Tech-News-Agentur 📰
Gründung der Tech-News-Agentur 📰
1
Aufgabe
JAVA 25 SELF, Level 50, Lektion 3
Gesperrt
Verbreitung heißer Nachrichten 🌐
Verbreitung heißer Nachrichten 🌐
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION