To podejście łączy elastyczność Observera z polimorficzną hierarchią zdarzeń. Zamiast wpisywać typ zdarzenia w sygnaturę metody, tworzysz klasę bazową Event i wyprowadzasz z niej konkretne typy. Jeden mechanizm subskrypcji obsługuje wtedy wiele typów zdarzeń, każdy z własnymi danymi, a bezpieczeństwo typów zostaje zachowane.

Czym to się różni od podejścia tradycyjnego

Tradycyjny Observer.

class Observer {
    virtual void on_value_changed(int value) = 0;
    virtual void on_state_changed(State state) = 0;
    virtual void on_error_occurred(std::string error) = 0;
};
// Każdy obserwator musi implementować wszystkie trzy metody,
// nawet jeśli dba tylko o jeden typ zdarzenia.

System oparty na zdarzeniach.

class EventListener {
    virtual void on_event(const Event& event) = 0;
};

class ValueChangedEvent : public Event { /* ... */ };
class StateChangedEvent : public Event { /* ... */ };
class ErrorOccurredEvent : public Event { /* ... */ };

// Obserwator otrzymuje wszystkie zdarzenia; używa RTTI lub visitora do wysłania
listener->on_event(ValueChangedEvent{10});
listener->on_event(StateChangedEvent{State::READY});

Korzyść. Nowe typy zdarzeń dodajesz bez zmiany interfejsu obserwatora. Obserwator może obsługiwać tylko te zdarzenia, które go interesują.

Kod

#include <iostream>
#include <vector>
#include <memory>
#include <typeinfo>
#include <string>
#include <sstream>

// ── Klasa bazowa Event ─────────────────────────────────────
class Event {
public:
    virtual ~Event() = default;
    virtual std::string type_name() const = 0;
    virtual void describe() const = 0;
};

// ── Konkretne typy Event ───────────────────────────────────
class ValueChangedEvent : public Event {
public:
    int old_value;
    int new_value;

    ValueChangedEvent(int old_val, int new_val)
        : old_value(old_val), new_value(new_val) {}

    std::string type_name() const override {
        return "ValueChangedEvent";
    }

    void describe() const override {
        std::cout << "  [Event] Wartość zmieniona: " << old_value 
                  << " → " << new_value << "\n";
    }
};

class StateChangedEvent : public Event {
public:
    enum class State { IDLE, LOADING, READY, ERROR };

    State old_state;
    State new_state;

    StateChangedEvent(State old, State new_st)
        : old_state(old), new_state(new_st) {}

    std::string type_name() const override {
        return "StateChangedEvent";
    }

    void describe() const override {
        auto state_name = [](State s) {
            switch(s) {
                case State::IDLE: return "IDLE";
                case State::LOADING: return "LOADING";
                case State::READY: return "READY";
                case State::ERROR: return "ERROR";
            }
            return "?";
        };

        std::cout << "  [Event] Stan zmieniony: " << state_name(old_state)
                  << " → " << state_name(new_state) << "\n";
    }
};

class ErrorOccurredEvent : public Event {
public:
    std::string message;

    explicit ErrorOccurredEvent(const std::string& msg)
        : message(msg) {}

    std::string type_name() const override {
        return "ErrorOccurredEvent";
    }

    void describe() const override {
        std::cout << "  [Event] Błąd: " << message << "\n";
    }
};

// ── Interfejs Event Listener ───────────────────────────────
class EventListener {
public:
    virtual ~EventListener() = default;
    virtual void on_event(const Event& event) = 0;
};

// ── Konkretni Listener'y ───────────────────────────────────

// Listener, któremu zależy na WSZYSTKICH zdarzeniach
class LoggingListener : public EventListener {
public:
    void on_event(const Event& event) override {
        std::cout << "[Logger] Odebrano " << event.type_name() << ":\n";
        event.describe();
    }
};

// Listener, któremu zależy tylko na zmianie wartości
class DisplayListener : public EventListener {
public:
    void on_event(const Event& event) override {
        // Podejście RTTI: sprawdzenie typu w runtime
        if (const auto* value_event = dynamic_cast<const ValueChangedEvent*>(&event)) {
            std::cout << "[Display] Nowa wartość: " << value_event->new_value << "\n";
        }
        // Ignoruj inne zdarzenia
    }
};

// Listener, któremu zależy tylko na błędach
class AlertListener : public EventListener {
public:
    void on_event(const Event& event) override {
        if (const auto* error_event = dynamic_cast<const ErrorOccurredEvent*>(&event)) {
            std::cout << "[Alert] " << error_event->message << "\n";
        }
    }
};

// Listener z selektywnym zainteresowaniem
class StateMonitor : public EventListener {
private:
    int value_changes_count_ = 0;
    int errors_count_ = 0;

public:
    void on_event(const Event& event) override {
        if (const auto* value_event = dynamic_cast<const ValueChangedEvent*>(&event)) {
            value_changes_count_++;
        } else if (const auto* error_event = dynamic_cast<const ErrorOccurredEvent*>(&event)) {
            errors_count_++;
            std::cout << "[Monitor] Błąd #" << errors_count_ << ": " 
                      << error_event->message << "\n";
        }
    }

    void report() const {
        std::cout << "\n[Monitor] Podsumowanie:\n";
        std::cout << "  - Zmiany wartości: " << value_changes_count_ << "\n";
        std::cout << "  - Błędy: " << errors_count_ << "\n";
    }
};

// ── Event Emitter ──────────────────────────────────────────
class EventSystem {
private:
    std::vector<std::unique_ptr<EventListener>> listeners_;

public:
    void subscribe(std::unique_ptr<EventListener> listener) {
        listeners_.push_back(std::move(listener));
    }

    void emit(const Event& event) {
        for (auto& listener : listeners_) {
            listener->on_event(event);
        }
    }

    size_t listener_count() const { return listeners_.size(); }
};

// ── Subject, który emituje zdarzenia ───────────────────────
class DataModel {
private:
    int value_ = 0;
    bool is_loaded_ = false;
    EventSystem& event_system_;

public:
    explicit DataModel(EventSystem& events)
        : event_system_(events) {}

    void set_value(int new_value) {
        if (value_ != new_value) {
            ValueChangedEvent event(value_, new_value);
            event_system_.emit(event);
            value_ = new_value;
        }
    }

    void load() {
        event_system_.emit(StateChangedEvent(
            StateChangedEvent::State::IDLE,
            StateChangedEvent::State::LOADING
        ));

        // Symuluj ładowanie...
        if (true) {  // Sukces
            event_system_.emit(StateChangedEvent(
                StateChangedEvent::State::LOADING,
                StateChangedEvent::State::READY
            ));
            is_loaded_ = true;
        } else {  // Porażka
            event_system_.emit(StateChangedEvent(
                StateChangedEvent::State::LOADING,
                StateChangedEvent::State::ERROR
            ));
            event_system_.emit(ErrorOccurredEvent("Ładowanie nie powiodło się"));
        }
    }
};

// ── Użycie ────────────────────────────────────────────────
int main() {
    EventSystem events;

    // Subskrybuj różnych listener'ów
    events.subscribe(std::make_unique<LoggingListener>());
    events.subscribe(std::make_unique<DisplayListener>());
    events.subscribe(std::make_unique<AlertListener>());

    auto* monitor = new StateMonitor();
    events.subscribe(std::unique_ptr<EventListener>(monitor));

    std::cout << "Połączono " << events.listener_count() << " listener'ów\n\n";

    DataModel model(events);

    std::cout << "Ustawianie wartości na 42:\n";
    model.set_value(42);

    std::cout << "\nŁadowanie danych:\n";
    model.load();

    std::cout << "\nUstawianie wartości na 100:\n";
    model.set_value(100);

    std::cout << "\nWyzwalanie błędu:\n";
    events.emit(ErrorOccurredEvent("Połączenie z bazą danych nie powiodło się"));

    monitor->report();

    return 0;
}

Wyjście

Połączono 4 listener'ów

Ustawianie wartości na 42:
[Logger] Odebrano ValueChangedEvent:
  [Event] Wartość zmieniona: 0 → 42
[Display] Nowa wartość: 42

Ładowanie danych:
[Logger] Odebrano StateChangedEvent:
  [Event] Stan zmieniony: IDLE → LOADING
[Logger] Odebrano StateChangedEvent:
  [Event] Stan zmieniony: LOADING → READY
[Display] Nowa wartość: 42

Ustawianie wartości na 100:
[Logger] Odebrano ValueChangedEvent:
  [Event] Wartość zmieniona: 42 → 100
[Display] Nowa wartość: 100

Wyzwalanie błędu:
[Logger] Odebrano ErrorOccurredEvent:
  [Event] Błąd: Połączenie z bazą danych nie powiodło się
[Alert] Połączenie z bazą danych nie powiodło się
[Monitor] Błąd #1: Połączenie z bazą danych nie powiodło się

[Monitor] Podsumowanie:
  - Zmiany wartości: 2
  - Błędy: 1

Warianty projektowe

1. Wzorzec Visitor do wysyłania zdarzeń

Zamiast dynamic_cast możesz użyć wzorca Visitor, żeby wysyłanie było bezpieczne typowo:

class EventVisitor {
public:
    virtual ~EventVisitor() = default;
    virtual void visit(ValueChangedEvent&) = 0;
    virtual void visit(StateChangedEvent&) = 0;
    virtual void visit(ErrorOccurredEvent&) = 0;
};

class Event {
public:
    virtual void accept(EventVisitor& visitor) = 0;
};

class ValueChangedEvent : public Event {
public:
    void accept(EventVisitor& v) override { v.visit(*this); }
    // ...
};

// W listener:
class DisplayListener : public EventListener, public EventVisitor {
public:
    void on_event(const Event& event) override {
        const_cast<Event&>(event).accept(*this);
    }

    void visit(ValueChangedEvent& e) override {
        std::cout << "[Display] " << e.new_value << "\n";
    }

    void visit(StateChangedEvent&) override { /* ignoruj */ }
    void visit(ErrorOccurredEvent&) override { /* ignoruj */ }
};

2. Zdarzenia typowane szablonami

Dla pełnego bezpieczeństwa typów bez RTTI:

template<typename EventType>
class TypedEventSystem {
private:
    std::vector<std::function<void(const EventType&)>> callbacks_;

public:
    void subscribe(std::function<void(const EventType&)> cb) {
        callbacks_.push_back(cb);
    }

    void emit(const EventType& event) {
        for (auto& cb : callbacks_) {
            cb(event);
        }
    }
};

// Użycie:
TypedEventSystem<ValueChangedEvent> value_system;
value_system.subscribe([](const ValueChangedEvent& e) {
    std::cout << "Wartość: " << e.new_value << "\n";
});

Zalety

  • Hierarchia typów. Zdarzenia tworzą drzewo dziedziczenia.
  • Selektywna obsługa. Listener zajmuje się tylko wybranymi zdarzeniami.
  • Rozszerzalność. Nowy typ zdarzenia nie wymaga zmiany interfejsu listenera.
  • Czytelne nazwy. Nazwa zdarzenia mówi, co się stało (ValueChangedEvent, a nie update()).
  • Wiele typów zdarzeń. Jeden mechanizm subskrypcji obsługuje różne powiadomienia.

Wady

  • Narzut RTTI. dynamic_cast kosztuje w czasie działania, a Visitor dodaje złożoność.
  • Łańcuch funkcji wirtualnych. Event::accept i listener::visit to kolejny poziom pośredniości.
  • Czas życia. Nadal potrzebujesz inteligentnych wskaźników (patrz następna strona).
  • Więcej kodu. Dla każdego typu powiadomienia powstaje osobna klasa zdarzenia.
  • Słabsze bezpieczeństwo typów. dynamic_cast omija sprawdzanie w kompilacji, choć istnieją bezpieczne alternatywy.

Kiedy stosować

  • Złożone systemy z wieloma typami zdarzeń.
  • Listenery interesują się tylko częścią zdarzeń.
  • Systemy, w których typy zdarzeń często się zmieniają.
  • Potrzebujesz nazw zdarzeń, które same mówią, co się stało.
  • Integrujesz się z systemami, które oczekują hierarchii zdarzeń.

Porównanie

Aspekt Tradycyjny Oparte na funkcjach Qt Signals System zdarzeń
Typy zdarzeń Jeden na sygnał Niejawny w sygnaturze Jeden na sygnał Hierarchia polimorficzna
Zakres listenera Musi obsłużyć wszystkie Osobno dla callbacku Wszystkie sloty dostają zdarzenie Selektywna obsługa
Dodawanie zdarzeń Zmiana interfejsu Dodanie sygnału Zmiana interfejsu Dziedziczenie po Event
Type safety Kompilacja Stracone Kompilacja Czas działania (RTTI)
Złożoność Niska Średnia Średnia Wysoka

Następne kroki