Beobachter-Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 94 von 104.
Das Beobachter-Muster definiert eine Eins-zu-viele-Abhängigkeit zwischen Objekten: Wenn ein Objekt (das Subjekt) seinen Zustand ändert, werden alle seine abhängigen Objekte (die Beobachter) 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 wesentliche Rollen: ein Subject, das eine Liste von Beobachtern verwaltet und sie benachrichtigt, sowie Observers, die eine Aktualisierungsschnittstelle 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, durchläuft das Subjekt alle registrierten Beobachter und ruft ihre update()-Methode auf. Beobachter können jederzeit angefügt oder entfernt werden, wodurch das System flexibel und lose gekoppelt bleibt.
Verwende Observer, wenn Änderungen an einem Objekt eine Aktualisierung anderer Objekte erfordern und du diese Objekte nicht eng miteinander koppeln möchtest.
Aufgabe
EinfachErstellen wir ein Weather Station-Überwachungssystem mithilfe des Observer-Musters. Du erstellst eine Wetterstation, die Temperaturdaten erfasst und mehrere Anzeigeeinheiten automatisch benachrichtigt, sobald sich die Temperatur ändert: eine klassische Anwendung dieses Musters aus der realen Welt.
Du organisierst deinen Code auf drei Dateien verteilt:
Observer.h: Definiere die Observer-Schnittstelle und einen konkreten Anzeige-Observer.Erstelle eine abstrakte
Observer-Klasse mit einer rein virtuellen Methodeupdate(double temperature), die Observer implementieren, um Benachrichtigungen zu empfangen, sowie einem virtuellen Destruktor.Erstelle anschließend eine
TemperatureDisplay-Klasse, die vonObservererbt. Jede Anzeige hat einen Namen (String), der über ihren Konstruktor festgelegt wird. Wennupdate()aufgerufen wird, soll sie Folgendes ausgeben:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Erstelle das Subjekt, das die Temperatur verwaltet und Observer benachrichtigt.Deine
WeatherStation-Klasse sollte:- Eine Liste von Observer-Zeigern und die aktuelle Temperatur speichern (mit
0.0initialisieren) - Eine
attach(Observer* obs)-Methode zum Registrieren von Observern besitzen - Eine
detach(Observer* obs)-Methode zum Entfernen von Observern besitzen - Eine
setTemperature(double temp)-Methode besitzen, die die Temperatur aktualisiert und alle angefügten Observer benachrichtigt - Eine private
notify()-Methode besitzen, die bei jedem Observerupdate()mit der aktuellen Temperatur aufruft
- Eine Liste von Observer-Zeigern und die aktuelle Temperatur speichern (mit
main.cpp: Veranschauliche das Observer-Muster in Aktion.Lies drei Eingaben ein:
- Name für die erste Anzeige (String)
- Name für die zweite Anzeige (String)
- Einen Temperaturwert (Double)
Erstelle eine
WeatherStationund zweiTemperatureDisplay-Objekte mit den angegebenen Namen. Füge beide Anzeigen zur Station hinzu und setze anschließend die Temperatur auf den Eingabewert. Beide Anzeigen sollten die Aktualisierung automatisch empfangen und ihre Nachrichten ausgeben.Entferne danach die erste Anzeige und setze eine neue Temperatur, die 5 Grad höher als der Eingabewert ist. Nur die zweite Anzeige sollte diese Aktualisierung empfangen.
Beispielsweise 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 degreesBeachte, wie das Observer-Muster ein lose gekoppeltes System schafft: Die Wetterstation muss nichts über die spezifischen Anzeigen wissen, sondern benachrichtigt einfach jeden, der zuhört. Wenn du einen Observer entfernst, empfängt er automatisch keine Aktualisierungen mehr.
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: Erstelle ein WeatherStation-Objekt
// TODO: Erstelle zwei TemperatureDisplay-Objekte mit den Eingabenamen
// TODO: Hänge beide Displays an die Wetterstation an
// TODO: Setze die Temperatur auf den Eingabewert
// (Beide Displays sollten ihre Nachrichten ausgeben)
// TODO: Hänge das erste Display ab
// TODO: Setze die Temperatur auf 5 Grad höher als die Eingabe
// (Nur das zweite Display sollte 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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie10STL-Übersicht
STL-Übersicht & PhilosophieSTL-ContainerIteratorenSTL-AlgorithmenFunktoren & Lambda-AusdrückeRückblick – Worthäufigkeit13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory und Abstract FactoryBuilder-MusterBeobachter-MusterStrategie-Muster2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler