Beobachter (Entwurfsmuster)

Entwurfsmuster

Das Beobachter-Muster (englisch observer pattern, auch listener pattern) ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung. Es gehört zur Kategorie der Verhaltensmuster (engl. behavioral patterns) und dient der Weitergabe von Änderungen an einem Objekt an von diesem Objekt abhängige Strukturen.[1] Das Muster ist eines der sogenannten GoF-Muster (Gang of Four; siehe Viererbande).

Neben publish-subscribe (kurz pub/sub) erfährt das Beobachter-Muster mit dem Signal-Slot-Konzept eine weitere Ausprägung.

Verwendung

Bearbeiten

Allgemeine Anwendungssituationen

Bearbeiten

Allgemein finden Beobachter-Muster Anwendung, wenn eine Abstraktion mehrere Aspekte hat, die von einem anderen Aspekt derselben Abstraktion abhängen, wo Änderung eines Objekts Änderungen an anderen Objekten nach sich zieht oder ein Objekt andere Objekte benachrichtigen soll, ohne diese im Detail zu kennen.

Anwendungsbeispiel

Bearbeiten

Eine oder auch mehrere Komponenten stellen den Zustand eines Objektes grafisch dar. Sie kennen die gesamte Schnittstelle dieses Objektes. Ändert sich der Zustand des Objektes, müssen die darstellenden Komponenten darüber informiert werden. Andererseits soll das Objekt aber von den Komponenten unabhängig bleiben, ihre Schnittstelle also nicht kennen.

Beispiel: Messergebnisse werden gleichzeitig in einem Balkendiagramm, einem Liniendiagramm und einer Tabelle dargestellt. Messwerte ändern sich permanent. Die Komponenten der Diagramme sollen diese Änderungen permanent darstellen, das gemessene Objekt soll dabei aber keine Kenntnis über die Struktur dieser Komponenten besitzen.

Das beobachtete Objekt bietet einen Mechanismus, um Beobachter an- und abzumelden und diese über Änderungen zu informieren. Es kennt alle seine Beobachter nur über die (überschaubare) Schnittstelle Beobachter. Änderungen werden völlig unspezifisch zwischen dem beobachteten Objekt und jedem angemeldeten Beobachter ausgetauscht. Dieses braucht also die weitere Struktur dieser Komponenten nicht zu kennen. Die Beobachter implementieren ihrerseits eine (spezifische) Methode, um auf die Änderung zu reagieren.

Man unterscheidet drei verschiedene Arten, das Beobachter-Muster umzusetzen:[2]

Push Notification
Jedes Mal wenn sich das beobachtete Objekt ändert, werden alle Beobachter benachrichtigt. Es werden jedoch keine Daten mitgeschickt, weshalb diese Form immer die gleiche Beobachter-Schnittstelle hat. Die Beobachter müssen nach Eintreffen der Nachricht Daten abholen.
Push-Update Notification
Jedes Mal wenn sich das beobachtete Objekt ändert, werden alle Beobachter benachrichtigt. Zusätzlich leitet das beobachtete Objekt die Update-Daten, die die Änderungen beschreiben, an die Beobachter weiter.
Pull Notification
Der Beobachter fragt selbstständig nach dem Zustand des beobachteten Objekts.

UML-Diagramm

Bearbeiten
 
Klassendiagramm, das die am Entwurfsmuster beteiligten Rollen zeigt.

Das folgende Klassendiagramm zeigt die am Entwurfsmuster beteiligten Rollen. Das Subjekt kann mehrere Beobachter haben, die unterschiedlichen konkreten Klassen angehören können.

Ein Subjekt (beobachtbares Objekt, auf Englisch publisher, also „Veröffentlicher“, genannt) hat eine Liste von Beobachtern, ohne deren konkrete Typen zu kennen. Es bietet eine Schnittstelle zur An- und Abmeldung von Beobachtern und eine Schnittstelle zur Benachrichtigung von Beobachtern über Änderungen an.

Ein konkretes Subjekt (konkretes, beobachtbares Objekt) speichert den relevanten Zustand und benachrichtigt alle Beobachter bei Zustandsänderungen über deren Aktualisierungsschnittstelle. Es verfügt über eine Schnittstelle zur Erfragung des aktuellen Zustands.

Die Beobachter (auf Englisch auch subscriber, also „Abonnent“, genannt) definieren eine Aktualisierungsschnittstelle.

Konkrete Beobachter verwalten die Referenz auf ein konkretes Subjekt, dessen Zustand sie beobachten und speichern und dessen Zustand konsistent ist. Sie implementieren eine Aktualisierungsschnittstelle unter Verwendung der Abfrageschnittstelle des konkreten Subjekts.

Vorteile

Bearbeiten

Subjekte und Beobachter können unabhängig variiert werden. Subjekt und Beobachter sind auf abstrakte und minimale Art lose gekoppelt. Das beobachtete Objekt braucht keine Kenntnis über die Struktur seiner Beobachter zu besitzen, sondern kennt diese nur über die Beobachter-Schnittstelle. Ein abhängiges Objekt erhält die Änderungen automatisch. Es werden auch Multicasts unterstützt.

Nachteile

Bearbeiten

Änderungen am Subjekt führen bei großer Beobachteranzahl zu hohen Änderungskosten. Außerdem informiert das Subjekt jeden Beobachter, auch wenn dieser die Änderungsinformation nicht benötigt. Zusätzlich können die Änderungen weitere Änderungen nach sich ziehen und so einen unerwartet hohen Aufwand haben.

Der Mechanismus liefert keine Information darüber, was sich geändert hat. Die daraus resultierende Unabhängigkeit der Komponenten kann sich allerdings auch als Vorteil herausstellen.

Ruft ein Beobachter während der Bearbeitung einer gemeldeten Änderung wiederum Änderungsmethoden des Subjektes auf, kann es zu Endlosschleifen kommen.

Typischerweise ist im Quellcode des Subjektes nicht erkennbar, welche Beobachter genau informiert werden. Es wird dadurch häufig schwer nachvollziehbar, welche Zustände das Programm bei einem Ereignis insgesamt durchläuft.

Beispiel in C++

Bearbeiten

Diese C++14 Implementierung basiert auf der vor C++98 Implementierung im Buch Entwurfsmuster.

#include <iostream>
#include <list>
#include <memory>

class Beobachter; // Beobachter Deklaration

class Subjekt {
public:
    Subjekt() :beobachtern() {}

    // kennt seine Beobachter. Eine beliebige Anzahl von Beobachtern kann ein Subjekt beobachten.

    void benachrichtige();

    // bietet eine Schnittstelle zum An- und Abmelden von Beobachtern.
    void meldeAn(Beobachter* beobachter) {
        beobachtern.push_front(beobachter);
    }

    void meldeAb(Beobachter* beobachter) {
        beobachtern.remove(beobachter);
    }

private:
    std::list<Beobachter*> beobachtern; // braucht Beobachter Deklaration
};

class Beobachter { // Beobachter Definition
public:
    // definiert eine Aktualisierungsschnittstelle für Objekte, die über Änderungen eines Subjekts benachrichtigt werden sollen.
    virtual void aktualisiere(Subjekt*) = 0;
    virtual ~Beobachter() = default;
};

void Subjekt::benachrichtige() {
    for (const auto& x:beobachtern)
        x->aktualisiere(this); // braucht Beobachter Definition
}

class KonkretesSubjekt: public Subjekt {
public:
    KonkretesSubjekt(): subjektZustand(0) {}

    int getZustand() {
        return subjektZustand;
    }

    void setZustand(int subjektZustand_) {
        subjektZustand = subjektZustand_;
        std::cout << "this=" << this << " subjektZustand=" << subjektZustand << '\n';
        // benachrichtigt seine Beobachter, wenn sich sein Zustand ändert.
        benachrichtige();
    }

private:
    // speichert den für KonkreterBeobachter relevanten Zustand.
    int subjektZustand;
};

class KonkreterBeobachter: public Beobachter {
public:
    KonkreterBeobachter(std::shared_ptr<KonkretesSubjekt> konkretesSubjekt_):
        beobachterZustand(0),
        konkretesSubjekt(konkretesSubjekt_.get()) {
        konkretesSubjekt->meldeAn(this);
    }

    KonkreterBeobachter(const KonkreterBeobachter&) = delete; // Dreierregel
    KonkreterBeobachter& operator=(const KonkreterBeobachter&) = delete;

    // implementiert die Aktualisierungsschnittstelle der Beobachterklasse, um seinen Zustand mit dem des Subjekts konsistent zu halten.
    void aktualisiere(Subjekt* subjekt) {
        if (subjekt == konkretesSubjekt) {
            beobachterZustand = konkretesSubjekt->getZustand();
            std::cout << "this=" << this << " beobachterZustand=" << beobachterZustand << '\n';
        }
    }

    virtual ~KonkreterBeobachter() {
        konkretesSubjekt->meldeAb(this);
    }

private:
    // speichert den Zustand, der mit dem des Subjekts in Einklang stehen soll.
    int beobachterZustand;
    // verwaltet eine Referenz auf ein KonkretesSubjekt.
    KonkretesSubjekt* konkretesSubjekt;
};

int main() {
    // Dynamischer Speicher (Heap). Smart pointers verhindern Memory Leaks.
    std::shared_ptr<KonkretesSubjekt> ks = std::make_shared<KonkretesSubjekt>();
    std::unique_ptr<KonkreterBeobachter> kb1 = std::make_unique<KonkreterBeobachter>(ks);
    std::unique_ptr<KonkreterBeobachter> kb2 = std::make_unique<KonkreterBeobachter>(ks);
    ks->setZustand(42);
}

Die Programmausgabe ist ähnlich zu:

this=0xad4e80 subjektZustand=42
this=0xad4ef0 beobachterZustand=42
this=0xad4eb0 beobachterZustand=42

Sonstiges

Bearbeiten

Bei der gerade durchgeführten Beobachtung eines Objektzustands kann es notwendig sein, einen konsistenten Subjektzustand zu garantieren. Dies kann durch synchrone Aufrufe der Notifizierungsmethode des Beobachters sichergestellt werden. (In einem Multithreading-System sind eventuell Synchronisationsmechanismen wie Sperren oder Warteschlangen zur Benachrichtigung der Beobachter erforderlich.)

Manche Programmiersprachen (wie beispielsweise Java) bieten eine Standard-Implementierung zum Beobachter-Muster an.[3] Allerdings führt eine solche Implementierung bei einer Programmiersprache, die keine multiple Vererbung von Klassen unterstützt, dazu, dass das Subjekt keine weiteren Klassen beerben kann, da es schon die Implementierung des Observer-Patterns erbt.

Verwandte Entwurfsmuster

Bearbeiten

Ein Vermittler kann zwischen Subjekten und Beobachtern vermitteln.

Bearbeiten
Wikibooks: Muster: Observer – Lern- und Lehrmaterialien

Einzelnachweise

Bearbeiten
  1. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster. 5. Auflage. Addison-Wesley, 1996, ISBN 3-8273-1862-9, S. 287.
  2. Bernd Brügge, Allen H. Dutoit: Objektorientierte Softwaretechnik: mit UML, Entwurfsmustern und Java. 2., überarbeitete Auflage. Addison-Wesley Verlag, 2004, ISBN 3-8273-7082-5.
  3. Observer Interface Javadoc. docs.oracle.com, abgerufen am 3. Juni 2015.