C++设计模式——观察者模式(Observer Pattern)

2023-10-30

C++设计模式——观察者模式(Observer Pattern)

微信公众号:幼儿园的学霸

目录

前言

观察者模式面向的需求是:A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者、小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会错过任何瞬间。程序里的观察者和这种真正的【观察】略有不同,观察者不需要时刻盯着被观察者(例如A不需要每隔1ms就检查一次B的状态),而是采用注册(Register)或者成为订阅(Subscribe)的方式告诉被观察者:我需要你的某某状态,你要在它变化时通知我。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。

定义

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
该模式属于行为型模式。

发布-订阅模式和观察者模式两者之间能否划上等号,网上的看法分为两极,众说纷纭。

看法一:发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber
看法二:在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识,两者完全不耦合。他们的交流是通过第三者,也就是在消息队列里面的Broker

我的看法: 模式区别除了实现外,最重要的是模式的目的性。显然两者都实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,从目的来看,两者是一样的。

有两大类(主题和观察者)一共四个角色
从上面我们可以看到,这里面包含了:

  • 抽象主题/抽象被观察者(Subject)角色:将所有观察者对象保存在一个集合中,可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象
  • 具体主题/具体被观察者(ConcreteSubject)角色:该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知
  • 抽象观察者(Observer)角色:它定义了一个更新接口,使得在得到主题/被观察者更新时通知自己
  • 具体观察者(ConcrereObserver)角色:实现抽象观察者定义的更新接口,以便在得到主题/被观察者更新时通知自己更新自身状态
    其UML类图如下:
    观察者模式UML类图

核心: 交互对象之间进行松耦合设计

代码示例

代码关键:
被观察对象自身应该包含一个容器来存放观察者对象,当被观察者自身发生改变时通知容器内所有的观察者对象自动更新。
观察者对象可以注册到被观察者的中,完成注册后可以检测被观察者的变化,接收被观察者的通知。当然观察者也可以被注销掉,停止对被观察者的监控。

#include <bits/stdc++.h>

//
//观察者模式
//

class Observer;
//抽象被观察者
class Subject {
public:
    Subject() : m_nState(0) {}

    virtual ~Subject() = default;

    virtual void Attach(const std::shared_ptr<Observer> pObserver) = 0;

    virtual void Detach(const std::shared_ptr<Observer> pObserver) = 0;

    virtual void Notify() = 0;

    virtual int GetState() { return m_nState; }

    void SetState(int state) {
        std::cout << "Subject updated !" << std::endl;
        m_nState = state;
    }

protected:
    std::list<std::shared_ptr<Observer>> m_pObserver_list;
    int m_nState;
};

//抽象观察者
class Observer {
public:
    virtual ~Observer() = default;

    Observer(const std::shared_ptr<Subject> pSubject, const std::string &name = "unknown")
            : m_pSubject(pSubject), m_strName(name) {}

    virtual void Update() = 0;

    virtual const std::string &name() { return m_strName; }

protected:
    std::shared_ptr<Subject> m_pSubject;
    std::string m_strName;
};

//具体被观察者
class ConcreteSubject : public Subject {
public:
    void Attach(const std::shared_ptr<Observer> pObserver) override {
        auto iter = std::find(m_pObserver_list.begin(), m_pObserver_list.end(), pObserver);
        if (iter == m_pObserver_list.end()) {
            std::cout << "Attach observer" << pObserver->name() << std::endl;
            m_pObserver_list.emplace_back(pObserver);
        }

    }

    void Detach(const std::shared_ptr<Observer> pObserver) override {
        std::cout << "Detach observer" << pObserver->name() << std::endl;
        m_pObserver_list.remove(pObserver);
    }

    //循环通知所有观察者
    void Notify() override {
        auto it = m_pObserver_list.begin();
        while (it != m_pObserver_list.end()) {
            (*it++)->Update();
        }
    }
};


//具体观察者1
class Observer1 : public Observer {
public:
    Observer1(const std::shared_ptr<Subject> pSubject, const std::string &name = "unknown")
            : Observer(pSubject, name) {}

    void Update() override {
        std::cout << "Observer1_" << m_strName << " get the update.New state is: "
                  << m_pSubject->GetState() << std::endl;
    }
};

//具体观察者2
class Observer2 : public Observer {
public:
    Observer2(const std::shared_ptr<Subject> pSubject, const std::string &name = "unknown")
            : Observer(pSubject, name) {}

    void Update() override {
        std::cout << "Observer2_" << m_strName << " get the update.New state is: "
                  << m_pSubject->GetState() << std::endl;
    }
};


int main() {

    std::shared_ptr<Subject> pSubject = std::make_shared<ConcreteSubject>();// 创建被观察者

    // 创建观察者
    std::shared_ptr<Observer> pObserver1_1 = std::make_shared<Observer1>(pSubject, "1");
    std::shared_ptr<Observer> pObserver1_2 = std::make_shared<Observer1>(pSubject, "2");
    std::shared_ptr<Observer> pObserver1_3 = std::make_shared<Observer1>(pSubject, "3");

    std::shared_ptr<Observer> pObserver2_4 = std::make_shared<Observer2>(pSubject, "4");
    std::shared_ptr<Observer> pObserver2_5 = std::make_shared<Observer2>(pSubject, "5");
    std::shared_ptr<Observer> pObserver2_6 = std::make_shared<Observer2>(pSubject, "6");

    // 注册观察者
    pSubject->Attach(pObserver1_1);
    pSubject->Attach(pObserver1_2);
    pSubject->Attach(pObserver1_3);
    pSubject->Attach(pObserver2_4);
    pSubject->Attach(pObserver2_5);
    pSubject->Attach(pObserver2_6);

    pSubject->SetState(2);// 改变状态
    pSubject->Notify();

    std::cout << std::string(50, '-') << std::endl;

    // 注销观察者
    pSubject->Detach(pObserver1_1);
    pSubject->Detach(pObserver1_2);

    pSubject->SetState(3);
    pSubject->Notify();

    return 0;
    //运行结果如下:
    //Attach observer1
    //Attach observer2
    //Attach observer3
    //Attach observer4
    //Attach observer5
    //Attach observer6
    //Subject updated !
    //Observer1_1 get the update.New state is: 2
    //Observer1_2 get the update.New state is: 2
    //Observer1_3 get the update.New state is: 2
    //Observer2_4 get the update.New state is: 2
    //Observer2_5 get the update.New state is: 2
    //Observer2_6 get the update.New state is: 2
    //--------------------------------------------------
    //Detach observer1
    //Detach observer2
    //Subject updated !
    //Observer1_3 get the update.New state is: 3
    //Observer2_4 get the update.New state is: 3
    //Observer2_5 get the update.New state is: 3
    //Observer2_6 get the update.New state is: 3

}

总结

观察者模式和中介模式

两个模式的意图还是非常相像的:
1.都属于行为型模式
2.都为了处理一对多的关系
3.UML实现基本相同,都有集合管理业务对象的集合,都有循环通知的方法,符合单一职责原则。

but,两者之间区别还是蛮大的:
中介模式中存在Mediator和Colleague两个角色,Mediator一般不会主动发起事件去通知Colleague,而Colleague具有发送和接收消息的两种能力。Mediator一般不会是消息的源头,也不会是消息传输的终点,它充当中转站的角色。而Colleague既可以是消息的发起者,也可以是消息传输的接收者,这意味着消息是双向的。Colleague可以存在多个,中介者(mediator)强调的是同事(colleague)类之间的交互。
反观观察者模式,消息的发起者只有一个,即Subject,所有Observer都关注Subject的消息,Subject只能发消息,Observer只能收消息,即消息的通知是单向的。观察者模式强调的是目标改变后对观察者进行统一的通讯,即被观察者与观察者之间的交互,所有的观察者都是一样的

优缺点

  • 优点
    1.观察者和被观察者是抽象耦合的
    2.建立了一套触发机制
  • 缺点
    1.如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
    2.如果观察者和观察目标间有循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
    3.观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化

适用场景及应用示例

  • 适用场景
    1.一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用
    2.一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度
    3.一个对象必须通知其他对象,而并不知道这些对象是谁
    4.需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制
    5.跨系统的消息变换场景,如消息队列的处理机制

  • 应用实例
    1.手机丢了,委托别人给其他人发消息通知
    2.通知老师/老板来了
    3.拍卖,拍卖师观察最高标价,然后通知给其它竞价者竞价
    4.猫叫了一声,吓着了老鼠,也惊到了主人,猫是被观察者,老鼠和人是观察者
    5.西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作

观察者模式解除了主体和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。

参考资料

1.简说设计模式——观察者模式
2.观察者模式
3.设计模式之观察者模式
4.设计模式专辑——中介模式、观察者模式的比较



下面的是我的公众号二维码图片,按需关注。
图注:幼儿园的学霸

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++设计模式——观察者模式(Observer Pattern) 的相关文章

  • 为什么相同的代码在同一台计算机上的执行时间可能不同?

    我是 C 编程新手 我编写了代码并希望获得它的运行时 这就是我所做的 每次运行代码时 我都会得到不同的运行时值 这样对吗 或者我的代码有问题吗 int main int argc char argv time t start end sta
  • 如何在 Visual Studio 2010 中增强 XAML 设计器?

    当我使用 XAML 设计器时 进入设计器和退出设计器是如此困难和缓慢 当我这样做时 Visual Studio 卡了一段时间 有什么方法可以增强 XAML 设计器和编辑器吗 Ant 保存 XAML 文件时非常慢 这通常意味着您可能有复杂的
  • 使用 Unity 在构造函数中使用属性依赖注入

    好的 我在基类中定义了一个依赖属性 我尝试在其派生类的构造函数内部使用它 但这不起作用 该属性显示为 null Unity 在使用 container Resolve 解析实例后解析依赖属性 我的另一种选择是将 IUnityContaine
  • 使用Physics.Raycast 和Physics2D.Raycast 检测对象上的点击

    我的场景中有一个空的游戏对象 带有 2D 组件盒碰撞器 我将脚本附加到该游戏对象 void OnMouseDown Debug Log clic 但是当我点击我的游戏对象时 没有任何效果 你有什么想法 如何检测我的盒子碰撞器上的点击 使用光
  • 如何从 .resx 文件条目获取注释

    资源文件中的字符串有名称 值和注释 The ResXResourceReader类让我可以访问名称和值 有办法看评论吗 你应该能够得到Comment via ResXDataNode class http msdn microsoft co
  • 如何访问另一个窗体上的ListView控件

    当单击与 ListView 所在表单不同的表单中的按钮时 我试图填充 ListView 我在 Form1 中创建了一个方法以在 Form2 中使用 并将参数传递给 Form1 中的方法 然后填充 ListView 当我调试时 我得到了传递的
  • 生成(非常)大的非重复整数序列而不进行预洗牌

    背景 我编写了一个简单的媒体客户端 服务器 我想生成一个不明显的时间值 随从客户端到服务器的每个命令一起发送 时间戳中将包含相当多的数据 纳秒分辨率 即使它不是真正准确 因为现代操作系统中计时器采样的限制 等 我想做的 在 Linux 上
  • C# Dns.GetHostEntry 不返回连接到 WiFi 的移动设备的名称

    我有一个 C 中的 Windows 窗体应用程序 我试图获取列表中所有客户端的主机名 下面给出的是 ra00l 来自此链接的代码示例 GetHostEntry 非常慢 https stackoverflow com questions 99
  • 未经许可更改内存值

    我有一个二维数组 当我第一次打印数组的数据时 日期打印正确 但其他时候 array last i 的数据从 i 0 到 last 1 显然是一个逻辑错误 但我不明白原因 因为我复制并粘贴了 for 语句 那么 C 更改数据吗 I use g
  • Visual Studio 中的测试单独成功,但一组失败

    当我在 Visual Studio 中单独运行测试时 它们都顺利通过 然而 当我同时运行所有这些时 有些通过 有些失败 我尝试在每个测试方法之间暂停 1 秒 但没有成功 有任何想法吗 在此先感谢您的帮助 你们可能有一些共享数据 检查正在使用
  • 如何在 Blackberry Cascades 中显示具有特定号码的电话板

    我正在使用带有 C QT 和 QML 的 Blackberry Cascades 10 Beta 3 SDK 以及 Blackberry 10 Dev Alpha Simulator 和 QNX Momentics IDE 并且我正在尝试实
  • 私有模板函数

    我有一堂课 C h class C private template
  • HttpWebRequest 在第二次调用时超时

    为什么以下代码在第二次 及后续 运行时超时 代码挂在 using Stream objStream request GetResponse GetResponseStream 然后引发 WebException 表示请求已超时 我已经尝试过
  • 如何从main方法调用业务对象类?

    我已将代码分为业务对象 访问层 如下所示 void Main Business object public class ExpenseBO public void MakeExpense ExpensePayload payload var
  • gcc 的配置选项如何确定默认枚举大小(短或非短)?

    我尝试了一些 gcc 编译器来查看默认枚举大小是否很短 至少一个字节 强制使用 fshort enums 或无短 至少 4 个字节 强制使用 fno short enums user host echo Static assert 4 si
  • C++ 密码屏蔽

    我正在编写一个代码来接收密码输入 下面是我的代码 程序运行良好 但问题是除了数字和字母字符之外的其他键也被读取 例如删除 插入等 我知道如何避免它吗 特q string pw char c while c 13 Loop until Ent
  • Process.Start() 方法在什么情况下返回 false?

    From MSDN https msdn microsoft com en us library e8zac0ca v vs 110 aspx 返回值 true 表示有新的进程资源 开始了 如果由 FileName 成员指定的进程资源 St
  • 如何在按钮单击时模拟按键 - Unity

    我对 Unity 中的脚本编写非常陌生 我正在尝试创建一个按钮 一旦单击它就需要模拟按下 F 键 要拾取一个项目 这是我当前的代码 在编写此代码之前我浏览了所有统一论坛 但找不到任何有效的东西 Code using System Colle
  • 线程和 fork()。我该如何处理呢? [复制]

    这个问题在这里已经有答案了 可能的重复 多线程程序中的fork https stackoverflow com questions 1235516 fork in multi threaded program 如果我有一个使用 fork 的
  • 在客户端系统中安装后桌面应用程序无法打开

    我目前正在使用 Visual Studio 2017 和 4 6 1 net 框架 我为桌面应用程序创建了安装文件 安装程序在我的系统中完美安装并运行 问题是安装程序在其他计算机上成功安装 但应用程序无法打开 edit 在客户端系统中下载了

随机推荐

  • 【C#学习笔记】Hello World

    using System namespace ConsoleApplication class Program static void Main string args Console Write Hello World Console R
  • Class.forName()用法详解

    一 CLASS类概念 Class也是一个Java类 保存的是与之对应Java类的meta信息 元信息 用来描述这个类的结构 比如描述一个类有哪些成员 有哪些方法等 一般在反射中使用 详细解释 Java 源程序 java 文件 在经过 Jav
  • mysql 查询不出结果_mysql 执行查询SQL 一直执行不出结果

    今天执行一个mysql 语句 一直在 执行 执行了5分钟了 还是没有出来结果 每个组织下包括 同级或者下级的 注册店铺数 查询 历史每一天的每个组织下的当天存在的门店数 select dt time startDate o brand id
  • Spring Cloud 序列化和反序列化过程定制(Jackson)

    现在都是基于Spring Cloud Feign进行微服务的调用 并且序列化的过程都封装完成的 只是自己可以定制序列化的方式 但是为了调用的时候能方便的找到问题所在等 基本都会使用json Jackson等 方式的序列化 虽然性能比较差 但
  • 学习笔记-弗洛伊德算法

    弗洛伊德算法解决最短路径问题 弗洛伊德算法和迪杰斯特拉算法都可以求解最短路径的问题 但区别是迪杰斯特拉会求出某个顶点到其他顶点的最短路径 而弗洛伊德算法会求出各个顶点到各个顶点的最短路径 最终结果采用一个二维表表示 而迪杰斯特拉算法只需要用
  • 文件读写的并发操作分析

    前言 涉及到多进程 线程间对文件的并发读写 首先说明一下比较常见的多进程读写方法是在保证数据不混乱的前提下 让某一个进程专门负责写该文件 其它进程负责往该进程发消息 通常在日志系统中 开启一个专门的进程 线程进行文件的写操作 其他进程 线程
  • Windows下用pip安装lib时报错的简单解决思路

    今天在Windows下用pip尝试安装Python爬虫库Scrapy 但是安装的时候报错 不过看命令行里也能运行scrapy命令 以为没问题 结果写了个简单爬虫 不停地报各种错 怀疑可能是这个系统中的pip版本问题 想了想 可以先重装pip
  • 操作系统中的硬链接和符号链接的不同

    硬链接和符号链接 这里没有介绍它们的功能和优点缺点 太多博客已经写过了 主要是解释了它们的原理 出现硬链接和符号链接的原因 我们需要共享文件 如果一个共享文件同时出现在属于不同用户的不同目录下 工作起来就会很方便 但是 怎么解决不同用户下对
  • 在未来的十年无疑将是刷脸支付的世界

    在未来的十年 交易场景的闭环 无疑将是刷脸支付的世界 最近比较热的词就是 刷脸支付 相对扫码支付 刷脸支付的方式更加安全 更加便捷 支付宝和微信同时推出刷脸支付 微信叫 青蛙 用户只需站在屏幕前 输入支付金额即可完成刷脸支付 按住屏幕下方的
  • vue项目性能优化详解汇总

    提起性能优化 很多人眼前浮现的面试经验是不是历历在目呢 反正 性能优化在我看来他永远是前端领域的热度之王 先说一下性能优化的方案 一 基础优化 代码以及编码规范 1 v if 和 v show 区分使用场景 v if false时不渲染DO
  • python输入姓名_Python基础篇--输入与输出

    在任何语言中 输入和输出都是代码最基础的开始 so 先来聊一聊输入和输出 输出 在python中 我们一般用print 输出 在括号里输入你想输出的信息 用引号包裹起来 单双三都可以 例如我们来输出一个 hello python gt gt
  • 【endnote】利用endnote批量修改参考文献格式

    1 情况描述 本来是在word中用交叉引用插入了参考文献 没有用endnote 但是格式没有统一 现在需要把全部参考文献统一成gb7714的格式 大概三百多篇 2 方法 1 在谷歌学术中挨个搜索参考文献 点击引用 点击 endnote 下载
  • Ubuntu14.04桥接网络设置与SSH登陆

    操作系统 Unbuntu14 04 虚拟机 VMware10 一 网络设置 1 设置vmware Bridge Protocol 本地链接 gt 属性 gt vmware Bridge Protocol打钩 2 然后主机设定静态ip 如果已
  • Matlab学习6-图像处理之直方图处理、灰度变换

    1 直方图均衡化 代码 直方图均衡化 img imread img rice png 显示 subplot 2 2 1 imshow img xlabel 原图 subplot 2 2 2 imhist img xlabel 原图的灰度直方
  • qt中漂亮的几款 qss 样式

    Qt中漂亮的几款QSS Shared QStackedWidget QLabel QPushButton QRadioButton QCheckBox QGroupBox QStatusBar QToolButton QComboBox Q
  • 傅里叶变换和小波分析

    无论是傅立叶变换还是小波变换 其实质都是一样的 既 将信号在时间域和频率域之间相互转换 从看似复杂的数据中找出一些直观的信息 再对它进行分析 由于信号往往在频域有比在时域更加简单和直观的特性 所以 大部分信号分析的工作是在频域中进行的 音乐
  • 【嵌入式】STM32基于片内flash进行数据读取和音频播放

    目录 一 片内FLASH的认识 二 如何对闪存进行读取 编程和擦除 三 基于flash的数据提取 项目创建 电路连接 调试 四 基于flash的提示音播放 五 实验心得 六 参考链接 一 片内FLASH的认识 不同型号的 STM32 其 F
  • phpStorm MarkDown插件下载

    phpStorm MarkDown插件下载 打开设置 找到Plugins Browse Repositories 搜索MarkDown Navigator 然后就可以正常查看 md 文件了 原文链接 https blog csdn net
  • element-ui table中使用type=‘selection‘实现多选、禁用的问题总结

    问题1 在表格中使用type selection 实现多选 但表头中用label无法添加全选字样 解决方法 使用css伪类元素添加 el table header has gutter tr el table column selectio
  • C++设计模式——观察者模式(Observer Pattern)

    C 设计模式 观察者模式 Observer Pattern 微信公众号 幼儿园的学霸 目录 文章目录 C 设计模式 观察者模式 Observer Pattern 目录 前言 定义 代码示例 总结 观察者模式和中介模式 优缺点 适用场景及应用