【转载】C++11 改进我们的模式之改进观察者模式

271 阅读3分钟

什么是观察者模式?当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象(广播通知)。观察者模式属于行为型模式。

image.png

原文地址:(原创) C++11 改进我们的模式之改进观察者模式

我对文章的格式和错别字进行了调整,并在他的基础上把重点部分进一步解释完善。 以下是正文。

正文

和单例模式面临的是同样的问题,主题更新的接口难以统一,很难做出一个通用的观察者模式,还是用到 可变参数模板 解决这个问题,其次还用到了右值引用,避免多余的内存移动。C++11 版本的观察者模式支持注册的观察者为 函数函数对象lambda 表达式,也避免了虚函数调用,更简洁更通用。

第一版本

直接看代码:

template <typename Func>
class Events
{
public:
    Events() : m_nextKey(0)
    {
    }
    ~Events() {}
    
    int Connect(Func &&f)
    {
        return Assgin(f);
    }

    int Connect(Func &f)
    {
        return Assgin(f);
    }

    /// @note 实际注册观察函数
    template <typename F>
    int Assgin(F &&f)
    {
        int k = m_nextKey++;
        m_connections[k] = f;
        return k;
    }
    
    /// @note 注销
    void Disconnect(int key)    
    {
        m_connections.erase(key);
    }

    /// @note 通知(执行)观察函数
    template <typename... Args>
    void Notify(Args &&...args)
    {
        for (auto &it : m_connections)
        {
            it.second(std::forward<Args>(args)...);
        }
    }

private:
    int m_nextKey;
    std::map<int, Func> m_connections; ///< 保存注册的观察函数
};

测试代码

struct stA
{
    int a, b;
};

/// @note 测试观察函数
void print(int a, int b) { cout << a << ", " << b << endl; }

void TestObserver()
{
    Events<std::function<void(int, int)>> myevent;

    auto key = myevent.Connect(print); ///< 以函数方式注册观察者
    
    stA t;
    auto lambdakey = myevent.Connect([&t](int a, int b)
                                     {
                                         t.a = a;
                                         t.b = b;
                                     }); ///< lambda 注册

    int a = 1, b = 2;
    myevent.Notify(a, b);    ///< 广播所有观察者
    myevent.Disconnect(key); ///< 移除观察者
}

第二版本

以下更新,增加了 +=-= 运算符重载,使用法和 C# 的 event 的用法接近。

#include <map>
template<typename Func>
class Events : NonCopyable
{
public:
    Events() :m_nextKey(0)
    {}
    ~Events(){}
    
    /// ------------ 主要新增 ------------ 
    int operator += (Func&& f)
    {
        return Connect(std::forward<Func>(f));
    }

    int operator += (Func& f)
    {
        return Connect(f);
    }

    template<typename... Args>
    void operator()(Args&&... args)
    {
        Notify(std::forward<Args>(args)...);
    }

    Events& operator -= (int key)
    {
        Disconnect(key);
        return *this;
    }
    /// ------------ 主要新增 ------------ 

    void Clear()
    {
        m_connections.clear();
    }

private:
    int Connect(Func&& f)
    {
        return Assgin(std::forward<Func>(f));
    }

    int Connect(Func& f)
    {
        return Assgin(f);
    }

    void Disconnect(int key)
    {
        m_connections.erase(key);
    }

    template<typename... Args>
    void Notify(Args&&... args)
    {
        for (auto& it : m_connections)
        {
            it.second(std::forward<Args>(args)...);
        }
    }

    template<typename F>
    int Assgin(F&& f)
    {
        int index = m_nextKey++;
        Push(index, std::forward<Func>(f));
        return index;
    }

    template<typename F>
    void Push(int index, F&& f)
    {
        m_connections.emplace(index, std::move(f));
    }

    template<typename F>
    void Push(int index, F& f)
    {
        m_connections.emplace(index, f);
    }

private:
    int m_nextKey;
    std::map<int, Func> m_connections;
};

测试代码

struct stA { 
    int a;
    int b;
    
    void fun(int x, int y)
    {
        a = x;
        b = y;
        cout << "a = " << a << " b= " << b << endl;
    }
};

void print(int a, int b) { cout << a << ", " << b << endl; }

void TestEvent()
{
    using Delegate1 = std::function<void(int, int)>;
    using Event1 = Events<Delegate1>;
    Event1 evnet1;  
    stA t;
    
    /// @note 添加委托
    auto key1 = evnet1 += &print; ///< 普通函数
    auto key2 = evnet1 += [&t](int a, int b){ ///< lambda 表达式
        t.a = a;
        t.b = b;
        cout << "t.a = " << t.a << " t.b = " << t.b << endl;
    };
    auto key3 = evnet1 += std::bind(&stA::fun, &t, std::placeholders::_1, std::placeholders::_2); ///< 函数对象
    
  /// @note 广播
    evnet1(2, 3);
    
  /// @note 移除委托
    evnet1 -= key1;
    evnet1 -= key2;
    
    /// @note 广播
    evnet1(4, 5);
    
  /// @note 清空事件
    evnet1.Clear();
    
    /// @note 广播
    evnet1(1, 2); ///< 清空了,所以什么都不会输出
}

输出结果

image.png

在第一个版本的基础上增加了 +=-= 运算符,使用法更接近 C#,这里 += 会返回一个 key,这个 key 用来 -= 删除委托时用到这种做法不太好,只是一个简单的处理。如果内部用 vector 的话,-= 时,根据 function 去删除指定的委托的话,用法就和 C# 完全一致了,不过,这里遇到的问题是 function 不支持比较操作,导致将 function 存入容器后,后面再根据 function 去删除时就找不到对应的 function 了。还没有足够的时间去研究这个问题,留到后面再想办法解决 function 比较的问题。

如果读者有更好的解决办法不妨提出来,讨论一下。