Wzorzec Observer
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 94 z 104.
Wzorzec Obserwator definiuje zależność jeden-do-wielu między obiektami: gdy jeden obiekt (podmiot) zmienia stan, wszystkie obiekty od niego zależne (obserwatorzy) są powiadamiane automatycznie. To idealne rozwiązanie dla systemów zdarzeń, aktualizacji interfejsu użytkownika lub każdej sytuacji, w której wiele obiektów musi reagować na zmiany.
Wzorzec obejmuje dwie kluczowe role: Podmiot, który przechowuje listę obserwatorów i powiadamia ich, oraz Obserwatorów, którzy implementują interfejs aktualizacji:
#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); // Oba wyświetlacze zostały powiadomione
}Gdy wywoływana jest metoda setState(), podmiot iteruje przez wszystkich zarejestrowanych obserwatorów i wywołuje ich metodę update(). Obserwatorzy mogą dołączać lub odłączać się w dowolnym momencie, dzięki czemu system jest elastyczny i luźno powiązany.
Użyj wzorca Observer, gdy zmiany w jednym obiekcie wymagają aktualizacji innych, a nie chcesz, aby te obiekty były ze sobą ściśle powiązane.
Wyzwanie
ŁatwyZbudujmy system monitorowania stacji pogodowej z użyciem wzorca Observer. Utworzysz stację pogodową, która śledzi dane o temperaturze i automatycznie powiadamia wiele wyświetlaczy za każdym razem, gdy temperatura się zmieni — to klasyczne zastosowanie tego wzorca w praktyce.
Rozmieścisz kod w trzech plikach:
Observer.h: Zdefiniuj interfejs obserwatora oraz konkretny obserwator wyświetlacza.Utwórz abstrakcyjną klasę
Observerz czysto wirtualną metodąupdate(double temperature), którą obserwatory zaimplementują, aby odbierać powiadomienia, oraz wirtualnym destruktorem.Następnie utwórz klasę
TemperatureDisplay, która dziedziczy poObserver. Każdy wyświetlacz ma nazwę (ciąg znaków) ustawianą przez konstruktor. Gdy wywołana zostanie metodaupdate(), powinna wypisać:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Utwórz podmiot, który przechowuje temperaturę i powiadamia obserwatorów.Twoja klasa
WeatherStationpowinna:- Przechowywać listę wskaźników na obserwatorów oraz bieżącą temperaturę (zainicjalizowaną wartością
0.0) - Mieć metodę
attach(Observer* obs)do rejestrowania obserwatorów - Mieć metodę
detach(Observer* obs)do usuwania obserwatorów - Mieć metodę
setTemperature(double temp), która aktualizuje temperaturę i powiadamia wszystkich dołączonych obserwatorów - Mieć prywatną metodę
notify(), która wywołujeupdate()dla każdego obserwatora, przekazując mu bieżącą temperaturę
- Przechowywać listę wskaźników na obserwatorów oraz bieżącą temperaturę (zainicjalizowaną wartością
main.cpp: Zademonstruj działanie wzorca Observer.Odczytaj trzy dane wejściowe:
- Nazwę pierwszego wyświetlacza (ciąg znaków)
- Nazwę drugiego wyświetlacza (ciąg znaków)
- Wartość temperatury (double)
Utwórz obiekt
WeatherStationi dwa obiektyTemperatureDisplayz podanymi nazwami. Dołącz oba wyświetlacze do stacji, a następnie ustaw temperaturę na wartość z danych wejściowych. Oba wyświetlacze powinny automatycznie otrzymać aktualizację i wypisać swoje komunikaty.Następnie odłącz pierwszy wyświetlacz i ustaw nową temperaturę, o 5 stopni wyższą niż wartość wejściowa. Tylko drugi wyświetlacz powinien otrzymać tę aktualizację.
Na przykład dla danych wejściowych Kitchen, Bedroom i 22.5:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degreesDla danych wejściowych Office, Lobby i 18.0:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesZwróć uwagę, że wzorzec Observer tworzy luźno powiązany system: stacja pogodowa nie musi nic wiedzieć o konkretnych wyświetlaczach — po prostu powiadamia tych, którzy nasłuchują. Po odłączeniu obserwator automatycznie przestaje otrzymywać aktualizacje.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: Utwórz obiekt WeatherStation
// TODO: Utwórz dwa obiekty TemperatureDisplay o podanych nazwach
// TODO: Dołącz oba wyświetlacze do stacji pogodowej
// TODO: Ustaw temperaturę na wartość wejściową
// (Oba wyświetlacze powinny wypisać swoje komunikaty)
// TODO: Odłącz pierwszy wyświetlacz
// TODO: Ustaw temperaturę o 5 stopni wyższą od wartości wejściowej
// (Tylko drugi wyświetlacz powinien wypisać komunikat)
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników10Przegląd STL
Przegląd i filozofia STLKontenery STLIteratoryAlgorytmy STLFunktory i wyrażenia lambdaPowtórzenie – częstość występowania słów13Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonFabryka i fabryka abstrakcyjnaWzorzec BuilderWzorzec ObserverWzorzec Strategy2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online