Observer-Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 94 von 104.
Das Observer-Muster definiert eine Eins-zu-viele-Abhängigkeit zwischen Objekten: Wenn ein Objekt (das Subject) seinen Zustand ändert, werden alle seine Abhängigen (die Observer) automatisch benachrichtigt. Dies ist ideal für Ereignissysteme, UI-Aktualisierungen oder jedes Szenario, in dem mehrere Objekte auf Änderungen reagieren müssen.
Das Muster umfasst zwei Schlüsselrollen: ein Subject, das eine Liste von Observern führt und diese benachrichtigt, und Observer, die eine Update-Schnittstelle implementieren:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // Beide Displays benachrichtigt
}Wenn setState() aufgerufen wird, iteriert das Subjekt durch alle registrierten Beobachter und ruft deren update()-Methode auf. Beobachter können sich jederzeit an- oder abmelden, was das System flexibel und lose gekoppelt macht.
Verwenden Sie das Observer-Muster, wenn Änderungen an einem Objekt die Aktualisierung anderer Objekte erfordern und Sie nicht möchten, dass diese Objekte eng miteinander gekoppelt sind.
Aufgabe
EinfachLassen Sie uns ein Weather Station Überwachungssystem mit dem Observer-Muster erstellen. Sie werden eine Wetterstation entwickeln, die Temperaturdaten verfolgt und automatisch mehrere Anzeigeeinheiten benachrichtigt, wann immer sich die Temperatur ändert – eine klassische reale Anwendung dieses Musters.
Sie werden Ihren Code über drei Dateien organisieren:
Observer.h: Definieren Sie das Observer-Interface und einen konkreten Display-Observer.Erstellen Sie eine abstrakte Klasse
Observermit einer rein virtuellen Methodeupdate(double temperature), die Observer implementieren, um Benachrichtigungen zu erhalten, zusammen mit einem virtuellen Destruktor.Erstellen Sie dann eine Klasse
TemperatureDisplay, die vonObservererbt. Jedes Display hat einen Namen (string), der über seinen Konstruktor gesetzt wird. Wennupdate()aufgerufen wird, sollte es Folgendes ausgeben:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Erstellen Sie das Subjekt, das die Temperatur verwaltet und die Observer benachrichtigt.Ihre Klasse
WeatherStationsollte:- Eine Liste von Observer-Pointern und die aktuelle Temperatur speichern (initialisieren Sie diese auf
0.0) - Eine Methode
attach(Observer* obs)haben, um Observer zu registrieren - Eine Methode
detach(Observer* obs)haben, um Observer zu entfernen - Eine Methode
setTemperature(double temp)haben, welche die Temperatur aktualisiert und alle angehängten Observer benachrichtigt - Eine private Methode
notify()haben, dieupdate()für jeden Observer mit der aktuellen Temperatur aufruft
- Eine Liste von Observer-Pointern und die aktuelle Temperatur speichern (initialisieren Sie diese auf
main.cpp: Demonstrieren Sie das Observer-Muster in Aktion.Lesen Sie drei Eingaben ein:
- Name für das erste Display (string)
- Name für das zweite Display (string)
- Ein Temperaturwert (double)
Erstellen Sie eine
WeatherStationund zweiTemperatureDisplay-Objekte mit den angegebenen Namen. Hängen Sie beide Displays an die Station an und setzen Sie dann die Temperatur auf den Eingabewert. Beide Displays sollten automatisch das Update erhalten und ihre Nachrichten ausgeben.Danach entfernen Sie das erste Display und setzen eine neue Temperatur, die 5 Grad höher als die Eingabe ist. Nur das zweite Display sollte dieses Update erhalten.
Zum Beispiel mit den Eingaben Kitchen, Bedroom und 22.5:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degreesMit den Eingaben Office, Lobby und 18.0:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesBeachten Sie, wie das Observer-Muster ein lose gekoppeltes System schafft – die Wetterstation muss nichts über die spezifischen Displays wissen, sie benachrichtigt einfach jeden, der zuhört. Wenn Sie einen Observer entfernen, hört er automatisch auf, Updates zu erhalten.
Spickzettel
Das Observer-Muster definiert eine Eins-zu-viele-Abhängigkeit, bei der ein Subjekt alle seine Beobachter automatisch benachrichtigt, wenn sich sein Zustand ändert.
Das Muster hat zwei Schlüsselrollen:
- Subject: Verwaltet eine Liste von Beobachtern und benachrichtigt diese über Zustandsänderungen
- Observer: Implementiert eine Update-Schnittstelle, um Benachrichtigungen zu erhalten
Basis-Implementierung:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};Wichtige Methoden:
attach(): Registriert einen Beobachter für den Empfang von Benachrichtigungendetach(): Entfernt einen Beobachter aus der Benachrichtigungslistenotify(): Iteriert durch alle Beobachter und ruft derenupdate()-Methode auf
Verwenden Sie das Observer-Muster, wenn Änderungen an einem Objekt die Aktualisierung anderer Objekte erfordern, ohne dass diese eng miteinander gekoppelt sind.
Probier es selbst
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// Eingaben lesen
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: Ein WeatherStation-Objekt erstellen
// TODO: Zwei TemperatureDisplay-Objekte mit den eingegebenen Namen erstellen
// TODO: Beide Displays an die WeatherStation anhängen
// TODO: Die Temperatur auf den eingegebenen Wert setzen
// (Beide Displays sollten ihre Nachrichten ausgeben)
// TODO: Das erste Display trennen
// TODO: Die Temperatur auf 5 Grad höher als die Eingabe setzen
// (Nur das zweite Display sollte etwas ausgeben)
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie10STL-Übersicht
STL-Übersicht & PhilosophieSTL-ContainerIteratorenSTL-AlgorithmenFunktoren & Lambda-AusdrückeRückblick – Wortfrequenz13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory & Abstrakte FabrikBuilder-MusterObserver-MusterStrategy-Muster2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container