Tradycyjny Observer opiera się na funkcjach wirtualnych i abstrakcyjnej klasie bazowej. Podmiot trzyma listę wskaźników do obserwatorów i wywołuje na każdym z nich update(), gdy stan się zmieni.
Struktura
Kod
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// ── Interfejs Observer ─────────────────────────────────────
class Observer {
public:
virtual ~Observer() = default;
virtual void update(int new_value) = 0;
};
// ── Subject (Wydawca) ──────────────────────────────────────
class Subject {
private:
int state_ = 0;
std::vector<Observer*> observers_;
public:
virtual ~Subject() = default;
void attach(Observer* observer) {
observers_.push_back(observer);
}
void detach(Observer* observer) {
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
observers_.erase(it);
}
}
void set_state(int new_state) {
if (state_ != new_state) {
state_ = new_state;
notify();
}
}
int get_state() const { return state_; }
private:
void notify() {
for (auto observer : observers_) {
observer->update(state_);
}
}
};
// ── Obserwatorzy konkretni ────────────────────────────────
class DisplayObserver : public Observer {
public:
void update(int new_value) override {
std::cout << "[Display] Stan zmienił się na: " << new_value << "\n";
}
};
class LoggingObserver : public Observer {
public:
void update(int new_value) override {
std::cout << "[Logger] Rejestrowanie zmiany stanu: " << new_value << "\n";
}
};
class AlertObserver : public Observer {
private:
int threshold_;
public:
explicit AlertObserver(int threshold) : threshold_(threshold) {}
void update(int new_value) override {
if (new_value > threshold_) {
std::cout << "[Alert] OSTRZEŻENIE: Stan " << new_value
<< " przekracza próg " << threshold_ << "\n";
}
}
};
// ── Użycie ────────────────────────────────────────────────
int main() {
Subject subject;
DisplayObserver display;
LoggingObserver logger;
AlertObserver alert(50);
// Rejestrowanie obserwatorów
subject.attach(&display);
subject.attach(&logger);
subject.attach(&alert);
std::cout << "Ustawianie stanu na 10:\n";
subject.set_state(10);
std::cout << "\nUstawianie stanu na 60:\n";
subject.set_state(60);
std::cout << "\nOdłączanie loggera, ustawianie stanu na 20:\n";
subject.detach(&logger);
subject.set_state(20);
return 0;
}
Wyjście
Ustawianie stanu na 10:
[Display] Stan zmienił się na: 10
[Logger] Rejestrowanie zmiany stanu: 10
Ustawianie stanu na 60:
[Display] Stan zmienił się na: 60
[Logger] Rejestrowanie zmiany stanu: 60
[Alert] OSTRZEŻENIE: Stan 60 przekracza próg 50
Odłączanie loggera, ustawianie stanu na 20:
[Display] Stan zmienił się na: 20
Zalety
- Proste i jasne. Funkcje wirtualne wprost pokazują intencję.
- Stan w obserwatorze. Każdy obserwator może trzymać własny stan.
- Wiele metod
update(). Można przeciążaćupdate()dla różnych typów powiadomień. - Elastyczna inicjalizacja. Obserwatorzy mogą dostać różne parametry, np.
AlertObserver(threshold). - Kontrola nad powiadomieniem. Sam decydujesz, kiedy i jak powiadamiać.
Wady
- Surowe wskaźniki. Czasem życia obserwatora trzeba zarządzać ręcznie. Jeśli usuniesz obserwatora, gdy jest dołączony, zostanie wiszący wskaźnik.
- Brak bezpieczeństwa typów. Wszyscy obserwatorzy dzielą jeden interfejs, więc informacja o typie ginie.
- Kolejność ma znaczenie. Nie masz kontroli nad kolejnością powiadomień.
- Narzut pamięci. Każdy obserwator potrzebuje wskaźnika do vtable.
- Sprzężenie z sygnaturą
update(). Dodanie parametru wymaga zmiany wszystkich implementacji.
Kiedy stosować
- Masz stabilny, znany zestaw typów obserwatorów.
- Obserwatorzy potrzebują parametrów inicjalizacyjnych lub własnego stanu.
- Chcesz jawnie kontrolować proces powiadamiania.
- Twój framework obsługuje polimorfizm, ale nie szablony, np. czysty C albo stara baza kodu.
Problemy z czasem życia
Poważny problem. Jeśli usuniesz obserwatora, gdy nadal jest dołączony, podmiot zostanie z wiszącym wskaźnikiem.
Subject subject;
DisplayObserver* observer = new DisplayObserver();
subject.attach(observer);
delete observer; // ❌ Obserwator usunięty, ale wciąż na liście podmiotu
subject.set_state(10); // ❌ Wywołuje update() usuniętego obserwatora → niezdefiniowane zachowanie!
Szczegółowe strategie oparte na RAII, inteligentnych wskaźnikach i weak_ptr znajdziesz na stronie Zarządzanie czasem życia.
Następne kroki
- Observer oparty na funkcjach (std::function). Użyj
std::function, jeśli chcesz większej elastyczności bez dziedziczenia. - Sygnały i sloty Qt. Mechanizm dostarczony przez framework, z automatycznym zarządzaniem czasem życia.
- Zorientowany obiektowo system zdarzeń. Zdarzenia zorientowane obiektowo, bezpieczne typowo i polimorficzne.
- Zarządzanie czasem życia. Rozwiązania problemów z czasem życia oparte na inteligentnych wskaźnikach.