Das Observer Pattern ist ein Verhaltensmuster, das die Abhängigkeit zwischen einem Objekt (dem „Subject“) und mehreren abhängigen Objekten (den „Observers“) definiert. Wenn sich der Zustand des „Subjects“ ändert, werden alle registrierten „Observers“ benachrichtigt und erhalten eine Aktualisierung. Dieses Muster fördert die Entkopplung von Objekten, da das „Subject“ nichts über die konkreten „Observers“ weiß. Es sendet lediglich Benachrichtigungen, wenn sich sein Zustand ändert.
Was ist das Observer Pattern?
Das Observer Pattern ermöglicht es, eine Eins-zu-Viele-Beziehung zwischen Objekten zu definieren. Ein „Subject“ hat eine Liste von „Observers“, die auf Änderungen reagieren. Das Pattern wird häufig in Benutzeroberflächen, Ereignisbenachrichtigungen und Systemen verwendet, in denen mehrere Komponenten denselben Zustand überwachen.
Komponenten des Observer Patterns
- Subject: Das Objekt, dessen Zustand überwacht wird. Es verwaltet eine Liste von „Observers“ und benachrichtigt sie bei Änderungen.
- Observer: Ein Interface oder eine abstrakte Klasse, die von allen Beobachtern implementiert wird. Es enthält eine Methode, die vom „Subject“ aufgerufen wird, um Benachrichtigungen zu senden.
- ConcreteSubject: Eine konkrete Implementierung des „Subject“, das den Zustand speichert und Änderungen benachrichtigt.
- ConcreteObserver: Eine konkrete Implementierung des „Observer“, die auf Änderungen des „Subject“ reagiert.
Beispiel des Observer Patterns in C++
Im folgenden Beispiel wird das Observer Pattern verwendet, um ein einfaches System zu modellieren, bei dem verschiedene Anzeigen den aktuellen Wert einer Temperatur überwachen.
#include <iostream>
#include <vector>
#include <memory>
// Observer: Abstrakte Klasse für die Beobachter
class Observer {
public:
virtual void update(float temperature) = 0;
virtual ~Observer() = default;
};
// Subject: Abstrakte Klasse für das Subjekt
class Subject {
public:
virtual void addObserver(std::shared_ptr<Observer> observer) = 0;
virtual void removeObserver(std::shared_ptr<Observer> observer) = 0;
virtual void notifyObservers() = 0;
virtual ~Subject() = default;
};
// ConcreteSubject: Konkrete Implementierung des Subjekts
class ConcreteSubject : public Subject {
private:
std::vector<std::shared_ptr<Observer>> observers;
float temperature;
public:
void setTemperature(float temp) {
temperature = temp;
notifyObservers();
}
float getTemperature() const {
return temperature;
}
void addObserver(std::shared_ptr<Observer> observer) override {
observers.push_back(observer);
}
void removeObserver(std::shared_ptr<Observer> observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notifyObservers() override {
for (const auto& observer : observers) {
observer->update(temperature);
}
}
};
// ConcreteObserver: Konkrete Implementierung des Beobachters
class ConcreteObserver : public Observer {
private:
std::string name;
public:
ConcreteObserver(const std::string& name) : name(name) {}
void update(float temperature) override {
std::cout << name << " updated with temperature: " << temperature << "°C" << std::endl;
}
};
// Client-Code
int main() {
// Subjekt und Beobachter erstellen
std::shared_ptr<ConcreteSubject> weatherStation = std::make_shared<ConcreteSubject>();
std::shared_ptr<ConcreteObserver> display1 = std::make_shared<ConcreteObserver>("Display 1");
std::shared_ptr<ConcreteObserver> display2 = std::make_shared<ConcreteObserver>("Display 2");
// Beobachter hinzufügen
weatherStation->addObserver(display1);
weatherStation->addObserver(display2);
// Temperatur setzen und Beobachter benachrichtigen
weatherStation->setTemperature(22.5f);
weatherStation->setTemperature(25.0f);
return 0;
}
Erklärung des C++-Beispiels
- Observer: Die abstrakte Klasse
Observer
enthält die Methodeupdate()
, die von den konkreten Beobachtern implementiert wird. Sie empfängt die Änderungen des Zustands. - Subject: Die abstrakte Klasse
Subject
definiert Methoden zum Hinzufügen und Entfernen von Beobachtern sowie die MethodenotifyObservers()
, um die Beobachter über Änderungen zu informieren. - ConcreteSubject: In der Klasse
ConcreteSubject
wird der Zustand (temperature
) gespeichert. Wenn sich der Zustand ändert, werden die registrierten Beobachter mit der MethodenotifyObservers()
benachrichtigt. - ConcreteObserver: Die Klasse
ConcreteObserver
implementiert das InterfaceObserver
. Sie reagiert auf Änderungen und gibt den neuen Temperaturwert aus. - Client: Der Client erstellt das
ConcreteSubject
und fügt zwei Beobachter hinzu. Danach wird der Temperaturwert geändert, und die Beobachter erhalten die Benachrichtigungen.
Vorteile des Observer Patterns
- Entkopplung: Das Observer Pattern entkoppelt das „Subject“ von seinen „Observers“. Das „Subject“ weiß nur, dass es eine Liste von Beobachtern hat, aber es muss nichts über deren Details wissen.
- Erweiterbarkeit: Neue Beobachter können hinzugefügt werden, ohne das „Subject“ zu ändern. Dadurch ist das System leicht erweiterbar.
- Benachrichtigungen: Das Pattern stellt sicher, dass alle Beobachter automatisch benachrichtigt werden, wenn sich der Zustand des Subjekts ändert.
- Flexibilität: Es ermöglicht, verschiedene „Observer“ zu erstellen, die auf den gleichen Zustand reagieren können, ohne ihre Implementierung zu kennen.
Nachteile des Observer Patterns
- Leistungsprobleme: Wenn viele Beobachter existieren, kann das Benachrichtigen aller Beobachter bei jeder Änderung des Zustands zu Leistungseinbußen führen.
- Unübersichtlichkeit: Wenn zu viele Beobachter gleichzeitig existieren, kann es schwer werden, die Übersicht zu behalten, insbesondere in großen Systemen.
- Unbeabsichtigte Benachrichtigungen: Wenn ein „Observer“ nicht richtig entfernt wird, kann er weiterhin Benachrichtigungen erhalten, was zu ungewollten Effekten führen kann.
Beispiel Observer Pattern in Python
Das Observer Pattern ist ein Verhaltensmuster, bei dem ein Subjekt (auch als „Publisher“ bezeichnet) eine Liste von Abonnenten (auch als „Observers“ bezeichnet) pflegt, die über Änderungen am Subjekt informiert werden, ohne dass das Subjekt genau weiß, wer diese Abonnenten sind. Hier ist ein einfaches Beispiel in Python:
Beispielcode:
# Observer-Interface
class Observer:
def update(self, message: str):
pass
# Konkreter Observer
class ConcreteObserver(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
print(f"{self.name} hat die Nachricht erhalten: {message}")
# Subjekt
class Subject:
def __init__(self):
self._observers = []
def add_observer(self, observer: Observer):
if observer not in self._observers:
self._observers.append(observer)
def remove_observer(self, observer: Observer):
if observer in self._observers:
self._observers.remove(observer)
def notify_observers(self, message: str):
for observer in self._observers:
observer.update(message)
# Beispiel-Nutzung des Observer Patterns
if __name__ == "__main__":
# Subjekt
subject = Subject()
# Beobachter
observer1 = ConcreteObserver("Beobachter 1")
observer2 = ConcreteObserver("Beobachter 2")
# Beobachter zum Subjekt hinzufügen
subject.add_observer(observer1)
subject.add_observer(observer2)
# Benachrichtigung an alle Beobachter senden
subject.notify_observers("Es gibt eine neue Nachricht!")
# Beobachter entfernen
subject.remove_observer(observer1)
# Neue Nachricht nach Entfernen eines Beobachters
subject.notify_observers("Es gibt eine weitere Nachricht!")
Erklärung:
- Observer (Beobachter): Dies ist ein Interface (oder abstrakte Klasse), das eine Methode
update
enthält, die von den konkreten Beobachtern implementiert wird. Jeder Beobachter muss diese Methode überschreiben, um auf Benachrichtigungen zu reagieren. - ConcreteObserver: Eine konkrete Implementierung des
Observer
-Interfaces. Der Beobachter speichert den Namen und gibt die empfangenen Nachrichten aus. - Subject (Subjekt): Die Klasse, die eine Liste von Beobachtern verwaltet. Es ermöglicht das Hinzufügen und Entfernen von Beobachtern und sendet Benachrichtigungen an alle registrierten Beobachter, wenn eine Änderung stattfindet.
- notify_observers: Diese Methode informiert alle registrierten Beobachter, indem sie deren
update
-Methode aufruft.
Ausgabe:
Beobachter 1 hat die Nachricht erhalten: Es gibt eine neue Nachricht!
Beobachter 2 hat die Nachricht erhalten: Es gibt eine neue Nachricht!
Es gibt eine weitere Nachricht!
Beobachter 2 hat die Nachricht erhalten: Es gibt eine weitere Nachricht!
In diesem Beispiel können Sie beobachten, wie das Subjekt (in diesem Fall Subject
) Änderungen an den Beobachtern (wie ConcreteObserver
) mitteilt, und wie Beobachter hinzufügt und entfernt werden.
Ist MVC ein Observer Pattern?
Das Model-View-Controller (MVC)-Muster und das Observer Pattern sind miteinander verwandte Konzepte, aber sie sind nicht dasselbe. Es gibt jedoch Ähnlichkeiten, insbesondere in der Art und Weise, wie das Model und die View miteinander interagieren. Lassen Sie uns die Unterschiede und Gemeinsamkeiten genauer betrachten:
1. Model-View-Controller (MVC)
Das MVC-Muster ist ein Entwurfsmuster, das eine Anwendung in drei Hauptkomponenten unterteilt:
- Model: Stellt die Daten und die Logik der Anwendung dar. Es ist für das Speichern und Verarbeiten der Daten verantwortlich und kann Benachrichtigungen an die View senden, wenn sich die Daten ändern.
- View: Die Benutzeroberfläche, die die Daten aus dem Model darstellt. Sie zeigt dem Benutzer die Informationen an und reagiert auf Eingaben.
- Controller: Die Komponente, die als Vermittler zwischen Model und View fungiert. Sie empfängt Benutzerereignisse (z. B. Klicks oder Tastatureingaben), verarbeitet sie und ändert bei Bedarf das Model oder die View.
2. Observer Pattern
Das Observer Pattern ist ein Verhaltensmuster, das darauf abzielt, eine One-to-Many-Beziehung zwischen einem Subjekt (dem Model) und seinen Beobachtern (den Views) zu ermöglichen. Wenn sich der Zustand des Subjekts ändert, werden alle registrierten Beobachter automatisch benachrichtigt und aktualisiert.
Gemeinsamkeiten:
- In beiden Mustern gibt es eine Kommunikation zwischen dem Model und der View.
- Beide Muster verfolgen das Ziel, eine Trennung von Verantwortlichkeiten zu ermöglichen, sodass das Model nicht direkt wissen muss, wie es angezeigt wird (im MVC) oder wie es die Beobachter informiert (im Observer Pattern).
Im MVC-Muster könnte das Model als „Subjekt“ betrachtet werden, und die View als „Beobachter“. Wenn sich die Daten im Model ändern, wird die View benachrichtigt, um sich zu aktualisieren. Diese Interaktion ähnelt dem Observer Pattern, aber das MVC-Muster umfasst auch den Controller, der zwischen Model und View vermittelt, was das Observer Pattern nicht notwendigerweise tut.
Unterschiede:
- Kontrolle: Im MVC-Muster ist der Controller für die Verarbeitung der Eingaben des Benutzers zuständig und entscheidet, wie das Model und die View miteinander interagieren. Im Observer Pattern gibt es keine zentrale Instanz wie den Controller – stattdessen handeln das Subjekt (Model) und die Beobachter (View) selbst.
- Zweck: Das Observer Pattern wird häufig verwendet, um eine lose Kopplung zwischen dem Subjekt und seinen Beobachtern zu erreichen, ohne dass das Subjekt genau weiß, wie viele Beobachter es gibt. MVC hingegen konzentriert sich auf die Trennung von Anwendungslogik (Model), Darstellung (View) und Benutzerinteraktion (Controller).
Wann sollte ich ein Observer Pattern benutzen?
Das Observer Pattern ist besonders nützlich, wenn Sie eine lose Kopplung zwischen Objekten erreichen möchten, die auf Änderungen eines anderen Objekts reagieren müssen, ohne dass das beobachtende Objekt (Observer) direkt vom beobachteten Objekt (Subject) abhängt. Hier sind einige Szenarien, in denen das Observer Pattern besonders vorteilhaft ist:
1. Wenn mehrere Objekte über Änderungen informiert werden müssen:
Wenn eine Änderung in einem Objekt viele andere Objekte betreffen soll, aber ohne dass diese Objekte direkt miteinander kommunizieren müssen, ist das Observer Pattern ideal. Der Observer ermöglicht es, dass das Subjekt (das geänderte Objekt) seine Beobachter (die abhängigen Objekte) ohne direkte Verbindungen benachrichtigt.
Beispiel: In einem E-Commerce-System kann das Produktmodell (Subjekt) mehrere Views oder Benachrichtigungen (Beobachter) informieren, wenn der Preis eines Produkts geändert wird. Verschiedene Komponenten der Anwendung (z. B. Preisanzeige, Preiswarnungen, Rabattanzeigen) sollten alle informiert werden, aber sie wissen nicht, wie die anderen Komponenten implementiert sind.
2. Wenn Sie eine „One-to-Many“-Beziehung benötigen:
Das Observer Pattern ist eine gute Wahl, wenn ein einziges Subjekt (z. B. eine Datenquelle) mehrere Beobachter (z. B. verschiedene Views oder Listener) hat, die auf Ereignisse oder Datenänderungen reagieren müssen. Dabei bleibt die Kommunikation einseitig – das Subjekt informiert nur seine Beobachter.
Beispiel: In einer Finanzanwendung möchten Sie, dass alle Benutzeroberflächen (Views) automatisch aktualisiert werden, wenn sich die zugrunde liegenden Finanzdaten (Model) ändern. Der Observer Pattern stellt sicher, dass alle betroffenen Views benachrichtigt werden, wenn die Daten aktualisiert werden, ohne dass die Views direkt auf die Daten zugreifen müssen.
3. Wenn das Subjekt keine Kenntnis von den Beobachtern haben soll:
Das Observer Pattern fördert die lose Kopplung. Das Subjekt weiß nicht, welche spezifischen Beobachter es hat, es muss sie nur benachrichtigen, wenn sich sein Zustand ändert. Dies ist besonders dann hilfreich, wenn Sie dynamisch Beobachter hinzufügen oder entfernen möchten.
Beispiel: In einem Benachrichtigungssystem könnte das Benachrichtigungszentrum (Subjekt) seine Abonnenten (Beobachter) darüber informieren, dass eine neue Nachricht eingegangen ist. Die Abonnenten können jederzeit hinzugefügt oder entfernt werden, ohne dass das Benachrichtigungszentrum genau wissen muss, wer abonniert hat oder wie viele es sind.
4. Wenn Sie die Performance optimieren möchten:
Wenn sich Änderungen in einem Subjekt auf viele andere Komponenten auswirken, kann das Observer Pattern die Performance verbessern, indem es den Update-Prozess nur dann anstößt, wenn tatsächlich eine Änderung stattgefunden hat. Außerdem wird der Notifizierungsprozess durch den direkten Austausch zwischen Subjekt und Beobachtern vereinfacht.
Beispiel: In einer Grafikanwendung, die Benutzerinteraktionen verarbeitet, könnte das Zeichenmodell (Subjekt) den Zeichenbereich (Beobachter) über Änderungen informieren, damit die Anzeige nur dann aktualisiert wird, wenn es tatsächlich eine Änderung am Zeichenbereich gibt (z. B. nach einem Zeichenvorgang).
5. Wenn Sie auf Ereignisse reagieren müssen:
Das Observer Pattern eignet sich hervorragend, wenn ein System auf bestimmte Ereignisse reagieren muss, ohne dass das System genau wissen muss, wie viele Objekte auf diese Ereignisse reagieren. Beobachter können auf diese Ereignisse reagieren, sobald sie eintreten, und das Subjekt kann weiterhin seine Logik unabhängig davon ausführen.
Beispiel: In einer Benutzerschnittstelle (UI) könnten Ereignisse wie Tastatureingaben, Mausklicks oder Touch-Gesten zu Änderungen in der Ansicht führen. Wenn die Benutzeroberfläche so konzipiert ist, dass sie dynamisch auf diese Ereignisse reagiert, könnte das Observer Pattern verwendet werden, um die Trennung von Event-Handling und Logik zu ermöglichen.
6. Wenn Sie eine „Veröffentlichung/Abonnement“-Architektur benötigen:
Wenn Sie ein System entwerfen, in dem sich Abonnenten für Updates zu bestimmten Ereignissen anmelden können (und die Subjekte diese Ereignisse auslösen), ist das Observer Pattern ein passendes Designmuster. Ein Abonnent wird automatisch benachrichtigt, wenn neue Daten oder Ereignisse veröffentlicht werden.
Beispiel: In einem Nachrichtensystem könnten sich Benutzer (Abonnenten) für bestimmte Themen anmelden (z. B. Sportnachrichten). Wenn eine neue Nachricht über das Thema veröffentlicht wird, werden alle Abonnenten über das neue Ereignis informiert.
7. Wenn Zustandsänderungen für das System oder die Benutzer relevant sind:
Wenn Zustandsänderungen im System oder in einem bestimmten Modell für andere Teile der Anwendung oder für Benutzer wichtig sind, können Sie das Observer Pattern verwenden, um den relevanten Code zu trennen und die Zustandsänderungen effizient weiterzugeben.
Beispiel: In einer Wetteranwendung könnten verschiedene Schnittstellen (Webseite, App, Desktop-Anwendung) über Änderungen von Wetterdaten informiert werden, z. B. Temperatur oder Luftfeuchtigkeit. Das Wettermodell (Subjekt) informiert die Views über Änderungen, ohne die Views direkt zu steuern.
Wann sollten Sie kein Observer Pattern verwenden?
- Wenn es nur wenige Abhängigkeiten gibt und die Änderung in einem Subjekt nur eine kleine Anzahl von Objekten betrifft, kann der Overhead des Observer Patterns unnötig sein.
- Wenn Sie enge Abhängigkeiten zwischen den Objekten haben und eine direkte Kommunikation erforderlich ist (z. B. in kleineren, nicht komplexen Systemen), kann ein direkter Aufruf sinnvoller sein.
- Wenn Sie Performance-Probleme haben, weil zu viele Benachrichtigungen gesendet werden und die Anzahl der Beobachter zu hoch ist, könnte es besser sein, nach einer effizienteren Lösung zu suchen.
Zusammenfassung:
Verwenden Sie das Observer Pattern, wenn:
- Es mehrere Abonnenten gibt, die über Änderungen informiert werden müssen.
- Sie eine lose Kopplung zwischen dem Subjekt und den Beobachtern benötigen.
- Sie eine „One-to-Many“-Beziehung zwischen Objekten haben.
- Sie Ereignisse oder Änderungen ohne direkte Abhängigkeiten zwischen den Komponenten weitergeben müssen.
Das Observer Pattern eignet sich besonders gut in Systemen, in denen sich der Zustand eines Objekts ändern kann und verschiedene Komponenten diese Änderung erkennen und darauf reagieren müssen.
Fazit
Das Observer Pattern ist ein mächtiges Muster, um die Kommunikation zwischen Objekten zu verwalten. Dabei entkoppelt es das „Subject“ von den „Observers“ und ermöglicht es, dass alle Beobachter automatisch benachrichtigt werden, wenn sich der Zustand ändert. Das Beispiel in C++ zeigt, wie einfach das Muster implementiert werden kann, um ein flexibles und erweiterbares Benachrichtigungssystem zu schaffen. Darum eignet es sich besonders für Szenarien, in denen mehrere Objekte den gleichen Zustand überwachen müssen, ohne direkt miteinander verbunden zu sein.
Zurück zur Pattern-Liste: Liste der Design-Pattern