观察者模式Observer

设计模式概念
设计模式简单来说就是在解决某一类问题场景时,有既定的,优秀的代码框架可以直接使用,与我们自己摸索出来的问题解决之道相比较,有以下优点可取:

代码更易于维护,代码的可读性,复用性,可移植性,健壮性会更好
当软件原有需求有变更或者增加新的需求时,合理的设计模式的应用,能够做到软件设计要求的“开-闭原则”,即对修改关闭,对扩展开放,使软件原有功能修改,新功能扩充非常灵活
合理的设计模式的选择,会使软件设计更加模块化,积极的做到软件设计遵循的根本原则“高内聚,低耦合”
因此掌握常用的设计模式非常有必要,无论是找工作,还是对于我们自己的项目软件设计,都很有帮助。

观察者模式简介
观察者模式又叫做观察者(Observer)监听者(Listener)模式,也叫发布(Publish)订阅(Subscribe)模式,常用于解耦事件的观察和事件最终的处理方式,举个例子说明:

例子:基于同一组数据,生成了很多不同的界面来显示,有曲线图显示方式,有圆饼图显示方式,有柱状图显示方式等等…,当底层数据发生改变时,所有基于同一组数据的图像显示都需要修改图像,那么此时有两种实现方式

所有图形界面模块都去观察底层数据是否做了改变,如果变化,那么读取数据,修改图像显示
此时有一个类似Observer观察者的模块,专门用来观察数据的变化,对某组数据感兴趣的图形界面模块,事先可以向Observer观察者注册,它对哪些数据的变化感兴趣,那么当Observer观察者观测到数据真的发生变化,它会及时的通知对这组数据变化感兴趣的图形界面模块执行相应的代码操作。
很明显,上面的第2种方式,解耦了事件的观察和事件最终的处理程序,各个图形界面模块相当于是监听者Listener或者订阅者Subscriber,观察数据变化的模块称作观察者Observer或者发布者Publisher。

简述MVC模式
这里为什么讲MVC模式,是因为在学习的过程中,很多人不知怎的,将观察者模式和MVC混为一谈。MVC模式最开始出现在WEB开发中,该模式能够很好的做到软件模块的高内聚,低耦合,所以其思想逐渐在各个软件开发领域都有涉及并且引用,MVC模式并不是OOP面向对象的设计模式,它是一种软件整体的架构思想。

M:指的是Model,数据模型层,表示系统底层的数据操作模块
V:指的是View,视图显示层,表示系统对于数据的展示模块
C:指的是Controller,控制层,表示监听用户事件交互,分发处理事件模块

一般来说,MVC模式就是通过Controller控制层监听用户的请求事件,通过访问Model数据层,对数据进行相应的增删改查操作,然后找到合适的View视图用相应的数据进行渲染生成最终的显示视图(可以是图形界面,html web页面,json,protobuff等等),然后再把视图返回给用户。

观察者模式的UML图

上图中,简单的罗列了观察者模式的类设计关系,Listener是对某些事件感兴趣的监听者,Listener1和Listener2是监听者具体的实现类,Observer是观察者,负责具体的事件观察并通知监听者处理已发生的事件。

一、观察者模式的代码实现
下面给出一个C++实现的观察者模式的代码示例:

#include <iostream>
#include <list>
#include <unordered_map>
#include <string>
#include <algorithm>
using namespace std;

// 定义监听者基类
class Listener
{
public:
    // 基类构造函数
    Listener(string name) :_name(name) {}
    // 监听者处理消息事件的纯虚函数接口
    virtual void handleMessage(int msgid) = 0;
protected:
    string _name;
};

// 一个具体的监听者类Listener1
class Listener1 : public Listener
{
public:
    Listener1(string name) :Listener(name) {}
    // Listener1处理自己感兴趣的事件
    void handleMessage(int msgid)
    {
        cout << "listener:" << _name << " recv:" << msgid
            << " msg, handle it now!" << endl;
    } 
};

// 一个具体的监听者类Listener2
class Listener2 : public Listener
{
public:
    Listener2(string name) :Listener(name) {}
    // Listener2处理自己感兴趣的事件
    void handleMessage(int msgid)
    {
        cout << "listener:" << _name << " recv:" << msgid
            << " msg, handle it now!" << endl;
    }
};

// 实现观察者
class Observer
{
public:
    /*
    params:
    1. Listener *pListener: 具体的监听者
    2. int msgid: 监听者感兴趣的事件
    该函数接口主要用于监听者向观察者注册感兴趣的事件
    */
    void registerListener(Listener *pListener, int msgid)
    {
        auto it = listenerMap.find(msgid);
        if (it == listenerMap.end())
        {
            // 没人对msgid事件感兴趣过,第一次注册
            listenerMap[msgid].push_front(pListener);
        }
        else
        {
            // 直接把当前pListener添加到对msgid事件感兴趣的list列表中
            it->second.push_front(pListener);
        }
    }

    /*
    params:
    1. int msgid:观察到发生的事件id
    该函数接口主要用于观察者观察到事件发生,并转发到对该事件感兴趣
    的监听者
    */
    void dispatchMessage(int msgid)
    {
        auto it = listenerMap.find(msgid);
        if (it != listenerMap.end())
        {
            for_each(it->second.begin(),
                it->second.end(),
                [&msgid](Listener *pListener)->void
            {
                // 观察者派生事件到感兴趣的监听者,监听者通过handleMessage接口负责事件的具体处理操作
                pListener->handleMessage(msgid);
            });
        }
    }
private:
    // 存储监听者注册的感兴趣的事件
    unordered_map<int, list<Listener*>> listenerMap;
};

int main()
{
    Listener *p1 = new Listener1("高海山");
    Listener *p2 = new Listener2("冯丽婷");

    Observer obser;
    // 监听者p1注册1,2,3号事件
    obser.registerListener(p1, 1);
    obser.registerListener(p1, 2);
    obser.registerListener(p1, 3);
    // 监听者p2注册1,3号事件
    obser.registerListener(p2, 1);
    obser.registerListener(p2, 3);

    // 模拟事件的发生
    int msgid;
    for (;;)
    {
        cout << "输入事件id:";
        cin >> msgid;

        if (-1 == msgid)
            break;
        // 通过用户手动输入msgid模拟事件发生,此处观察者派发事件
        obser.dispatchMessage(msgid);
    }

    return 0;
}

上面代码运行结果如下:

通过代码可以看到,监听者Listener向观察者Observer事先注册好它需要处理的感兴趣的事件后,然后就可以做自己的事情,当Observer观察到有相应的事件发生时,会对事件进行派发,通知对该事件感兴趣的监听者处理该事件,这就是观察者模式。

二、考虑观察者运行在多线程当中
考虑这样一个问题,如果观察者Observer独立的运行在一个线程环境中,那么当它观察到事件发生时,它是这样通知监听者处理事件的,代码如下:

for_each(it->second.begin(),
                it->second.end(),
                [&msgid](Listener *pListener)->void
            {
                //观察者派生事件到感兴趣的监听者,监听者通过handleMessage接口负责事件具体处理操作
                pListener->handleMessage(msgid);
            });
遍历一个list<Listener*>的列表,然后回调每一个监听者的handleMessage方法进行事件派发,那么这就涉及到在多线程环境中,共享对象的线程安全问题(解决方法就是使用智能指针),如果不明白,请参考我的另一篇博客,链接:https://blog.csdn.net/QIANGWEIYUAN/article/details/88562935

上面代码通过访问Listener*,也就是指向监听者的指针,在调用handleMessage时,其实在多线程环境中,肯定不明确此时监听者对象是否还存活,或是已经在其它线程中被析构了,此时再去通知这样的监听者,肯定是有问题的,也就是说,当Observer观察者运行在独立的线程中时,在通知监听者处理该事件时,应该先判断监听者对象是否存活,如果监听者对象已经析构,那么不用通知,并且需要从map表中删除这样的监听者对象。

那当然是使用shared_ptrweak_ptr智能指针了,上面使用Listener*裸指针在多线程环境中肯定存在多线程访问共享对象的线程安全问题,代码修改如下:

#include <iostream>
#include <list>
#include <unordered_map>
#include <string>
#include <algorithm>
#include <memory>
using namespace std;

// 定义监听者基类
class Listener
{
public:
    // 基类构造函数
    Listener(string name) :_name(name) {}
    // 监听者处理消息事件的纯虚函数接口
    virtual void handleMessage(int msgid) = 0;
protected:
    string _name;
};

// 一个具体的监听者类Listener1

class Listener1 : public Listener
{
public:
    Listener1(string name) :Listener(name) {}
    // Listener1处理自己感兴趣的事件
    void handleMessage(int msgid)
    {
        cout << "listener:" << _name << " recv:" << msgid
            << " msg, handle it now!" << endl;
    }
};

// 一个具体的监听者类Listener2
class Listener2 : public Listener
{
public:
    Listener2(string name) :Listener(name) {}
    // Listener2处理自己感兴趣的事件
    void handleMessage(int msgid)
    {
        cout << "listener:" << _name << " recv:" << msgid
            << " msg, handle it now!" << endl;
    }
};

// 实现观察者用强弱智能指针,解决多线程访问共享对象的线程安全问题
class Observer
{
public:
    /*
    params:
    1. Listener *pListener: 具体的监听者
    2. int msgid: 监听者感兴趣的事件
    该函数接口主要用于监听者向观察者注册感兴趣的事件
    */
    void registerListener(weak_ptr<Listener> pwListener, int msgid)
    {
        auto it = listenerMap.find(msgid);
        if (it == listenerMap.end())
        {
            // 没人对msgid事件感兴趣过,第一次注册
            listenerMap[msgid].push_front(pwListener);
        }
        else
        {
            // 直接把当前pListener添加到对msgid事件感兴趣的list列表中
            it->second.push_front(pwListener);
        }
    }
    /*
    params:
    1. int msgid:观察到发生的事件id
    该函数接口主要用于观察者观察到事件发生,并转发到对该事件感兴趣的监听者
    */
    void dispatchMessage(int msgid)
    {
        auto it = listenerMap.find(msgid);
        if (it != listenerMap.end())
        {
            for (auto it1 = it->second.begin();
                it1 != it->second.end();
                ++it1)
            {
                // 智能指针的提升操作,用来判断监听者对象是否存活
                shared_ptr<Listener> ps = it1->lock();
                // 监听者对象如果存活,才通知处理事件
                if (ps != nullptr)
                {
                    ps->handleMessage(msgid);
                }
                else
                {
                    // 监听者对象已经析构,从map中删除这样的监听者对象
                    it1 = it->second.erase(it1);
                }
            }
        }
    }
private:
    // 存储监听者注册的感兴趣的事件
    unordered_map<int, list<weak_ptr<Listener>>> listenerMap;
};

int main()
{
    shared_ptr<Listener> p1(new Listener1("高海山"));
    shared_ptr<Listener> p2(new Listener2("冯丽婷"));

    Observer obser;
    // 监听者p1注册1,2,3号事件
    obser.registerListener(p1, 1);
    obser.registerListener(p1, 2);
    obser.registerListener(p1, 3);
    // 监听者p2注册1,3号事件
    obser.registerListener(p2, 1);
    obser.registerListener(p2, 3);

    // 模拟事件的发生
    int msgid;
    for (;;)
    {
        cout << "输入事件id:";
        cin >> msgid;
        if (-1 == msgid)
            break;
        // 通过用户手动输入msgid模拟事件发生,此处观察者派发事件
        obser.dispatchMessage(msgid);
    }

    return 0;
}

原文链接:设计模式 - 观察者Observer模式_[&msgid](listener *plistener)->void-CSDN博客

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值