Das Beobachter-Muster (engl. Observer oder Listener) wird eingesetzt, wenn mehrere Objekte dauerhaft den Zustand eines anderen Objekts beobachten. Wenn sich der Zustand des beobachteten Objekts ändert, soll dieses alle seine Beobachter darüber informieren, damit diese sich ihrerseits reaktiv aktualisieren können. Das Muster entspricht also dem Prinzip: "Don't call us, we'll call you!"

Wir können uns folgendes Beispiel vorstellen: Das beobachtete Objekt ist ein Feld zur Datumsauswahl. Dessen Beobachter sind eine Aufgaben- und eine Terminliste, die ihren jeweiligen Zustand (also die Aufgaben und Termine, die anzuzeigen sind) in Abhängigkeit des ausgewählten Datums ändern.

Das beobachtete Objekt (engl. Observable) speichert häufig Daten in Form einer List<T> oder einer Map<T,T>, die im User Interface (UI) an verschiedenen Stellen mittels unterschiedlicher Sichten (engl. Views) dargestellt werden, z.B. als Tabelle oder Chart. Diese Sichten im UI sind dann die Beobachter, die ihre Darstellung bei einer Zustandsänderung des Observable aktualisieren müssen. Das grundsätzliche Ziel des Beobachter-Musters ist es also, Observable und Observer voneinander zu entkoppeln, was in der Anwendung häufig der Trennung von Modell und beobachtenden Sichten entspricht.

Die Abhängigkeit zwischen den Objekten kann im Software-Entwurf über unterschiedliche prinzipielle Wege abgebildet werden:

Das Beobachter-Muster wird demzufolge auch Publish-Subscribe-Muster genannt. Die Vorstellung hierbei entspricht einem Newsletter- oder Newsfeed-Abonnement: Beobachter abonnieren Nachrichten von Herausgebern, die diese gelegentlich veröffentlichen. Das Observable ist dabei der sendende Publisher, und die Observer sind die empfangenden Subscriber, die ihr Abonnement auch wieder kündigen können, falls sie sich nicht mehr für die Nachrichten interessieren.

Das folgende UML-Klassendiagramm visualisiert das Beobachter-Muster.

Ein Observable bietet Methoden an, um Beobachter zu registrieren (addObserver) und zu entfernen (deleteObserver) und verwaltet seine Beobachter in einer entsprechenden Liste (observers). Das folgende UML-Sequenzdiagramm verdeutlicht den grundlegenden Ablauf des Beobachter-Musters. Sobald der Zustand des konkreten Observable von außen geändert wird (setState), informiert das Observable über die Methode notifyObservers alle registrierten konkreten Beobachter, indem es deren Methode update aufruft und den neuen Zustand sowie sich selbst als Quelle dieses neuen Zustands weitergibt.

In einer alternativen Variante des Musters kann die Methode update auch ohne Argumente dargestellt werden. In diesem Fall muss ein Beobachter nach der Benachrichtigung sich den neuen Zustand selbst über eine entsprechende Methode getState vom Observable abholen.

Zur Anwendung des ursprünglichen Beobachter-Musters werden in Java java.util.Observable und java.util.Observer bereitgestellt. Jedoch sind beide seit Java 9 als deprecated gekennzeichnet und daher nicht weiter zu empfehlen. Das folgende Code-Beispiel demonstriert die Verwendung von Observable (Zeile 1) und Observer (Zeile 11), wobei Observer ein funktionales Interface (engl. Functional Interface) ist, d.h. das Interface schreibt genau eine Methode vor und kann daher in Lambda-Ausdrücken implementiert werden.

Alle im Folgenden gezeigten Code-Beispiele zum Entwurfsmuster Beobachter finden sich im Verzeichnis /patterns/observer des Modul-Repository.

class MyObservable extends Observable {
    Object state;

    void setState(Object newState) {
        this.setChanged();
        this.notifyObservers(state = newState);
    }

    public static void main(String[] args) {
        // Observer is implemented as a lambda expression
        Observer o1 = (source, newState) -> System.out.printf("Received new state = %s from %s\n", newState, source);

        MyObservable source = new MyObservable();
        source.addObserver(o1);

        List.of(1, 2, 3).forEach(i -> source.setState(i));
    }
}

Das folgende Code-Beispiel demonstriert die Verwendung eines PropertyChangeListener als neuere Alternative zu java.util.Observer. Anstatt java.util.Observable zu erweitern, kann die Liste der registrierten Beobachter und die Methode notifyObservers auch eigenständig implementiert werden (Zeilen 3-7). Die Beobachter können in diesem Fall das funktionale Interface java.beans.PropertyChangeListener implementieren und über dessen Methode ein PropertyChangeEvent empfangen (Zeilen 15-17).

class MySecondObservable {
    Object state;
    List<PropertyChangeListener> listeners = new ArrayList<>();

    void notifyListeners(String property, Object oldValue, Object newValue) {
        listeners.forEach(l -> l.propertyChange(new PropertyChangeEvent(this, property, oldValue, newValue)));
    }

    void setState(Object newState) {
        notifyListeners("state", state, state = newState);
    }

    public static void main(String[] args) {
        // Observer is implemented as a lambda expression
        PropertyChangeListener o1 = (PropertyChangeEvent e) ->
            System.out.printf("Received new state = %s [previous state = %s] from %s\n", 
                e.getNewValue(), e.getOldValue(), e.getSource());

        MySecondObservable source = new MySecondObservable();
        source.listeners.add(o1);

        List.of(1, 2, 3).forEach(i -> source.setState(i));
    }
}

Der PropertyChangeListener ist ein spezieller java.util.EventListener. In Java begegnen uns bei der UI-Programmierung häufig spezielle EventListener als Teil des Beobachter-Musters, um z.B. verschiedene UI-Komponenten wie Textfelder, Buttons u.ä. oder die Interaktion des Anwenders über Tastatur, Maus u.ä. zu beobachten. In JavaFX wird üblicherweise das Interface javafx.event.EventHandler, das auch eine EventListener-Spezialisierung ist, verwendet, um ein anderes Objekt zu beobachten und auf dessen Zustandsänderung zu reagieren:

interface EventHandler<T extends Event> extends EventListener {
    void handle​(T event);
}

Der folgende Code zeigt, wie in JavaFX die Tastatureingabe, die Mausbewegung und ein Button beobachtet werden können. Dabei sind Button und Scene durch JavaFX bereitgestellte Observables.

Scene scene = new Scene(/* ... */);
scene.setOnKeyPressed((KeyEvent e) -> System.out.println("Pressed key: " + e.getCode()));

Button btn = new Button(/* ... */);
btn.setOnMouseEntered((MouseEvent e) -> System.out.println("Moved mouse over button"));
btn.setOnAction((ActionEvent e) -> System.out.println("Clicked button"));

Die seit Java 9 neue Klasse java.util.concurrent.Flow stellt die Interfaces Publisher und Subscriber bereit, über die das Beobachter-Muster als asynchroner Datenstrom (engl. Stream) realisiert werden kann. Der folgende Code demonstriert eine grundlegende Verwendung der Flow-API.

class MySubscriber implements Subscriber<Integer> {
    Subscription subscription;

    @Override
    public void onSubscribe(Subscription subscription) {
        System.out.println("Subscription startet");
        this.subscription = subscription;
        subscription.request(1);
    }

    @Override
    public void onNext(Integer item) {
        System.out.printf("Received new state = %s\n", item);
        this.subscription.request(1);
    }

    @Override
    public void onError(Throwable throwable) { }

    @Override
    public void onComplete() { System.out.println("Subscription ended"); }
}

Ein konkreter Subscriber muss die Methoden onSubscribe, onNext, onError und onComplete implementieren. Beim initialen Aufruf von onSubscribe (Zeile 5) erhält der Subscriber ein Objekt vom Typ Subscription, über das er steuern kann, ob er in der Folge weitere Nachrichten erhalten möchte (Methode request ist auszuführen, Zeilen 8+14) oder nicht (Methode cancel). Falls ein Subscriber weitere Nachrichten erhalten möchte, werden ihm diese über die Methode onNext (Zeile 12) zugestellt.

class MyPublisher {
    public static void main(String[] args) throws InterruptedException {
        SubmissionPublisher source = new SubmissionPublisher<Integer>();
        source.subscribe(new MySubscriber());

        List.of(1, 2, 3).forEach(i -> source.submit(i));
        source.close();
        Thread.sleep(100); // wait for termination of subscriber thread
    }
}

Die Klasse SubmissionPublisher bietet eine Standard-Implementierung des Publisher-Interface. Nachrichten, die über die Methode submit (Zeile 6) eingereicht werden, sendet der Publisher an alle aktuell registrierten Subscriber, bis er über die Methode close (Zeile 7) geschlossen wird. Hervorzuheben ist, dass ein Publisher die Nachrichten asynchron zustellt, d.h. er wartet nicht auf eine Empfangs- oder Verarbeitungsbestätigung der Subscriber, die ihrerseits jeweils in einem parallelen Thread (s. Kapitel Threads in Java) ablaufen. Jeder aktuelle Subscriber erhält neu übermittelte Nachrichten in derselben Reihenfolge, wie sie versendet werden - außer bei Exceptions (onError) oder bei Timeouts. Wenn eine Nachricht über offer anstatt submit versendet wird, kann ein Timeout definiert werden, falls ein Subscriber zu lange braucht, um die Nachricht entgegenzunehmen. Auf diese Weise können Publisher als nicht blockierende, reaktive Streams fungieren. Damit der Subscriber-Thread ausreichend Zeit zur Verarbeitung der Nachrichten hat, schläft der Main-Thread im obigen Code eine Weile (Zeile 8). Das ist für die Praxis keine gute Lösung sondern ein Anti-Pattern, aber es trägt hier zur Erhaltung der Einfachheit des Beispiels bei.