使用观察者模式两次实现主题时使用了错误的观察者列表

问题描述 投票:0回答:1

我在 C++ 中实现了一个简单的观察者模式,如下所示:

template<typename T>
class Observer {
private:
    virtual void notify(const T& data) = 0;
public:
    virtual ~Observer() = default;
};

template<typename T>
class Subject {
public:
    void subscribe(Observer<T> *observer);
    void unsubscribe(Observer<T> *observer);
    virtual ~Subject() = default;
protected:
    void notify(const T& data);
private:
    std::list<Observer<T>*> observers;
};

template<typename T>
void Subject<T>::subscribe(Observer<T> *observer) {
    this->observers.push_back(observer);
}

template<typename T>
void Subject<T>::unsubscribe(Observer<T> *observer) {
    this->observers.remove(observer);
}

template<typename T>
void Subject<T>::notify(const T& data) {
    for (const auto &observer: this->observers) {
        observer->notify(data);
    }
}

它定义了两个类,Observer 和Subject。主题存储观察者列表并实现通知方法,该方法用某种数据通知所有观察者。

现在我实现了一个 Car 类,它继承了两次 subject,一个用于移动“事件”,一个用于转向“事件”:

class Car : public Subject<MoveData>, public Subject<SteerData> {
    ...
    void move() {
        const MoveData moveData{};
        this->Subject<MoveData>::notify(moveData);
    }

    void steer() {
        const SteerData steerData{};
        this->Subject<SteerData>::notify(steerData);
    }
    ...
}

如果我现在创建我的 Car 类的实例并调用 move 函数,那么会发生Subject基类的notify方法使用Subject基类的观察者列表,这会导致分段错误。

我的目标是 MoveData-Subject 的通知函数也使用存储在Subject基类中的观察者,无论我多久使用抽象Subject类作为基类。

c++ oop inheritance observer-pattern
1个回答
0
投票

更现代的方法是根本不再使用虚拟函数。 C++ 有 std::function 这是一个很好的替代方案,并且不需要您为要实现的每种新类型的观察者定义抽象基类。

在线演示:https://onlinegdb.com/Zy04AVXmQ 示例:

#include <iostream>
#include <functional>
#include <utility>
#include <unordered_map>


template<typename... args_t>
class Registrations
{
    class Registration
    {
    public:
        Registration(Registrations& registrations, std::size_t cookie) :
            m_cookie{ cookie },
            m_registrations{ registrations }
        {
        }

        ~Registration()
        {
            m_registrations.unsubscribe(m_cookie);
        }

    private:
        std::size_t m_cookie;
        Registrations& m_registrations;
    };


public:
    [[nodiscard]] Registration subscribe(std::function<void(args_t...)> handler)
    {
        static std::size_t cookie{ 0ul };

        ++cookie;
        m_handlers.insert({ cookie,handler });

        return Registration{ *this,cookie };
    }

    void notify(args_t...args)
    {
        for (auto& [_, handler] : m_handlers)
        {
            handler(args...);
        }
    }

private:
    void unsubscribe(std::size_t id)
    {
        m_handlers.erase(id);
    }

    std::unordered_map<std::size_t, std::function<void(args_t...)>> m_handlers;
};

class Car
{
public:
    auto OnSteeringAngleChanged(std::function<void(double)> handler)
    {
        return m_steeringAngleChanged.subscribe(handler);
    }

    void Steer(double angle)
    {
        m_steeringAngleChanged.notify(angle);
    }

private:
    Registrations<double> m_steeringAngleChanged;
};

int main()
{
    Car car;

    {
        auto subscription = car.OnSteeringAngleChanged([](double angle) { std::cout << angle << "\n"; });
        
        car.Steer(15.0); // handler function will be called, subscription is active. 
        car.Steer(25.0); // handler function will be called, subscription is active. 
        car.Steer(35.0); // handler function will be called, subscription is active. 
        
        // subscription will go out of scope and the handler will no longer be called
    }

    // no more subscription so no handler will be called.
    car.Steer(45.0);
}
© www.soinside.com 2019 - 2024. All rights reserved.