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

Mermaid diagram

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