“牵一发而动全身”——我用观察者模式简单模拟吃鸡
每当Jungle公众号【Jungle笔记】更新发布了文章,作为Jungle的关注者,你会第一时间接到消息,(如果)然后就可以去查看、点赞、评论和转发,接下来的一天你都高高兴兴;
每当Jungle更新了CSDN博客,作为Jungle的支持者,你也会在打开CSDN网站的时候看到消息,(如果)然后你就可以去查看、点赞、评论和转发,接下来的一周你都高高兴兴。
也就是说,“Jungle更新发布文章”这个行为可能会导致“关注者查看、评论、点赞”等一系列行为。这表明“Jungle更新发布文章”并不是孤立的,而是与众多对象产生了关联。一个对象行为的改变,其相关联的对象都会得到通知,并自动产生对应的行为。这在软件设计模式中,即是观察者模式。
1.观察者模式简介
软件系统中的对象并不是孤立存在的,一个对象行为的改变可能会引起其他所关联的对象的状态或行为也发生改变,即“牵一发而动全身”。观察者模式建立了一种一对多的联动,一个对象改变时将自动通知其他对象,其他对象将作出反应。观察者模式中,发生改变的对象称为“观察目标”,被通知的对象称为“观察者”。一个观察目标可以有很多个观察者。
观察者模式定义如下:
观察者模式:
定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。
观察者模式又被称为发布-订阅模式(Publish-Subscribe)、模型-视图模式(Model-View)、源-监听器模式(Source-Listener)、从属者模式(Dependents)。
2.观察者模式结构
观察者模式由观察者和观察目标组成,为便于扩展,两个角色都设计了抽象层。观察者模式的UML图如下:

- Subject(目标):是被观察的对象,目标中定义了一个观察者的集合,即一个目标可能会有多个观察者,通过attach()和detach()方法来增删观察者对象。目标声明了通知方法notify(),用于在自身状态发生改变时通知观察者。
- ConcreteSubject(具体目标):具体目标实现了通知方法notify(),同时具体目标有记录自身状态的属性和成员方法;
- Observer(观察者):观察者将对接收到的目标发生改变的通知做出自身的反应,抽象层声明了更新方法update();
- ConcreteObserver(具体观察者): 实现了更新方法update(),具体观察者中维护了一个具体目标对象的引用(指针),用于存储目标的状态。
下述是观察者模式的典型实现:
#ifndef __DEMO_H__
#define __DEMO_H__
// 抽象观察者
class Observer
{
public:
// 声明响应更新方法
virtual void update() = 0;
};
// 具体观察者
class ConcreteObserver:public Observer
{
public:
// 实现响应更新方法
void update(){
// 具体操作
}
};
// 抽象目标
class Subject
{
public:
// 添加观察者
void attach(Observer* obs){
obsList.push_back(obs);
}
// 移除观察者
void detach(Observer* obs){
obsList.remove(obs);
}
// 声明通知方法
virtual void notify() = 0;
protected:
// 观察者列表
list<Observer*>obsList;
};
// 具体目标
class ConcreteSubject :public Subject
{
public:
// 实现通知方法
void notify(){
// 具体操作
// 遍历通知观察者对象
for (int i = 0; i < obsList.size(); i++){
obsList[i]->update();
}
}
};
// 客户端代码示例
int main()
{
Subject *sub = new ConcreteSubject();
Observer *obs = new ConcreteObserver();
sub->attach(obs);
sub->notify();
return 0;
}
#endif //__DEMO_H__
3.观察者模式代码实例
玩过和平精英这款游戏吗?四人组队绝地求生,当一个队友发现物资时,可以发消息“我这里有物资”,其余三个队友听到后可以去取物资;当一个队友遇到危险时,也可以发消息“救救我”,其余三个队友得到消息后便立马赶去营救。本例Jungle将用观察者模式来模拟这个过程。
本例的UML图如下:

本例中,抽象观察者是Observer,声明了发现物资或者需要求救时的呼叫的方法call(),具体观察者是Player,即玩家,Player实现了呼叫call()方法,并且还定义了取物资come()和支援队友help()的方法。本例定义了AllyCenter作为抽象目标,它维护了一个玩家列表playerList,并且定义了加入战队和剔除玩家的方法。具体目标是联盟中心控制器AllyCenterController,它实现了通知notify()方法,该方法将队友call的消息传达给玩家列表里的其余队友,并作出相应的响应。源代码见https://github.com/FengJungle/DesignPattern。
3.0.公共头文件
通过一个枚举类型来定义两种消息类型,即发现物资和求助
#ifndef __COMMON_H__
#define __COMMON_H__
enum INFO_TYPE{
NONE,
RESOURCE,
HELP
};
#endif //__COMMON_H__
3.1.观察者
3.1.1.抽象观察者Observer
// 抽象观察者 Observer
class Observer
{
public:
Observer(){}
// 声明抽象方法
virtual void call(INFO_TYPE infoType, AllyCenter* ac) = 0;
string getName(){
return name;
}
void setName(string iName){
this->name = iName;
}
private:
string name;
};
3.1.2.具体观察者Player
// 具体观察者
class Player :public Observer
{
public:
Player(){
setName("none");
}
Player(string iName){
setName(iName);
}
// 实现
void call(INFO_TYPE infoType, AllyCenter* ac){
switch (infoType){
case RESOURCE:
printf("%s :我这里有物资\n", getName().c_str());
break;
case HELP:
printf("%s :救救我\n", getName().c_str());
break;
default:
printf("Nothing\n");
}
ac->notify(infoType, getName());
}
// 实现具体方法
void help(){
printf("%s:坚持住,我来救你!\n", getName().c_str());
}
void come(){
printf("%s:好的,我来取物资\n", getName().c_str());
}
};
3.2.目标类
3.2.1.抽象目标AllyCenter
声明
// 抽象目标:联盟中心
class AllyCenter
{
public:
AllyCenter();
// 声明通知方法
virtual void notify(INFO_TYPE infoType, std::string name) = 0;
// 加入玩家
void join(Observer* player);
// 移除玩家
void remove(Observer* player);
protected:
// 玩家列表
std::vector<Observer*>playerList;
};
实现
#include "AllyCenter.h"
#include "Observer.h"
AllyCenter::AllyCenter(){
printf("大吉大利,今晚吃鸡!\n");
}
// 加入玩家
void AllyCenter::join(Observer* player){
if (playerList.size() == 4){
printf("玩家已满\n");
return;
}
printf("玩家 %s 加入\n", player->getName().c_str());
playerList.push_back(player);
if (playerList.size() == 4){
printf("组队成功,不要怂,一起上!\n");
}
}
// 移除玩家
void AllyCenter::remove(Observer* player){
printf("玩家 %s 退出\n", player->getName().c_str());
//playerList.remove(player);
}
3.2.2.具体目标AllyCenterController
声明:
// 具体目标
class AllyCenterController :public AllyCenter
{
public:
AllyCenterController();
// 实现通知方法
void notify(INFO_TYPE infoType, std::string name);
};
实现:
AllyCenterController::AllyCenterController(){}
// 实现通知方法
void AllyCenterController::notify(INFO_TYPE infoType, std::string name){
switch (infoType){
case RESOURCE:
for each (Observer* obs in playerList){
if (obs->getName() != name){
((Player*)obs)->come();
}
}
break;
case HELP:
for each (Observer* obs in playerList){
if (obs->getName() != name){
((Player*)obs)->help();
}
}
break;
default:
printf("Nothing\n");
}
}
3.3.客户端代码示例及效果
#include "Observer.h"
#include "AllyCenter.h"
int main()
{
// 创建一个战队
AllyCenterController* controller = new AllyCenterController();
// 创建4个玩家,并加入战队
Player* Jungle = new Player("Jungle");
Player* Single = new Player("Single");
Player* Jianmengtu = new Player("贱萌兔");
Player* SillyDog = new Player("傻子狗");
controller->join(Jungle);
controller->join(Single);
controller->join(Jianmengtu);
controller->join(SillyDog);
printf("\n\n");
// Jungle发现物资,呼叫队友
Jungle->call(RESOURCE, controller);
printf("\n\n");
// 傻子狗遇到危险,求救队友
SillyDog->call(HELP, controller);
printf("\n\n");
system("pause");
return 0;
}
上述代码运行结果如下图:

4.观察者模式的应用
观察者模式是一种使用频率非常高的设计模式,几乎无处不在。凡是涉及一对一、一对多的对象交互场景,都可以使用观察者会模式。比如购物车,浏览商品时,往购物车里添加一件商品,会引起UI多方面的变化(购物车里商品数量、对应商铺的显示、价格的显示等);各种编程语言的GUI事件处理的实现;所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。
5.总结
优点:
- 观察者模式实现了稳定的消息更新和传递的机制,通过引入抽象层可以扩展不同的具体观察者角色;
- 支持广播通信,所有已注册的观察者(添加到目标列表中的对象)都会得到消息更新的通知,简化了一对多设计的难度;
- 符合开闭原则,增加新的观察者无需修改已有代码,在具体观察者与观察目标之间不存在关联关系的情况下增加新的观察目标也很方便。
缺点:
- 代码中观察者和观察目标相互引用,存在循环依赖,观察目标会触发二者循环调用,有引起系统崩溃的风险;
- 如果一个观察目标对象有很多直接和简介观察者,将所有的观察者都通知到会耗费大量时间。
适用环境:
- 一个对象的改变会引起其他对象的联动改变,但并不知道是哪些对象会产生改变以及产生什么样的改变;
- 如果需要设计一个链式触发的系统,可是使用观察者模式;
- 广播通信、消息更新通知等场景。
欢迎关注知乎专栏:Jungle是一个用Qt的工业Robot
欢迎关注Jungle的微信公众号:Jungle笔记

智能推荐
观察者模式
观察者模式 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。 意图 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 主要解决 一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。 何时使用 一个对象(目标...
观察者模式
观察者模式 在GOF的《设计模式:可复用面向对象软件的基础》一书中对观察者模式是这样说的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。当一个对象发生了变化,关注它的对象就会得到通知;这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者。 再说说上面的数据和图之间的关系;...
观察者模式
观察者模式: 定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。 认识观察者模式: 可以通过看报纸和杂志订阅来认识观察者模式: 报社的业务就是出版报纸。 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。 只要报社还在运营,就会一直有人(或单位...
观察者模式
简介 观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。 观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象...
Linux环境下配置和安装hadoop及hadoop集群搭建(VMware)
文章目录 一、安装准备 二、hadoop的配置 1.首先配置hadoop-env.sh 2.配置core-site.xml 3.配置hdfs-site.xml 4.配置mapred-site.xml 5.配置yarn-site.xml 6.配置slaves 7.配置hadoop环境变量 三、格式化HDFS 四、启动hadoop 五、集群搭建 1.克隆虚拟机 2.配置免密登录 3.修改主机器的配置文...
猜你喜欢
使用QProcess打开和关闭第三方应用,比如CMD
使用QProcess打开和关闭第三方应用,比如CMD 注意: 很多教程不一定是对的,但我这篇绝对是对的,因为我踩坑过啊。 为了节省时间,直接上图、上代码,so easy! 重要事情说3遍: 杀死进程,一定要加/F 和 /T 杀死进程,一定要加/F 和 /T 杀死进程,一定要加/F 和 /T 开始 验证下,打开任务管理器就能看到 总结 从上面看,是不是很简单,taskkill不知道是啥,是windo...
自定义View实现注销图案的加载动画
先看效果图: 有那味了。。。(懂得都懂^ ^ √) 我们先来分析一下怎么画,然后再研究怎么让他动起来 这个View是由内部的注销图案和外面一圈圆环构成。而内部的注销图案又是由一个基本满角度的圆弧和一根竖线组成 一、绘制内部注销图案 首先初始化画笔和圆弧的外切矩形: 圆弧的中心是View的中心,坐标为(getWidth()/2,getWidth()/2),半径设置为getWidth/4,...
vue3使用vue-count-to组件
项目场景: 数据可视化大屏开发的过程中,需要实现一种滚动数字的效果,在使用vue2时,使用vue-count-to完全没有问题,功能也比较完善(滚动时长,开始值,结束值,前缀,后缀,千分隔符,小数分隔符等等),但是在vue3中使用会出现问题。 展示的效果 问题描述: 出现的错误时 == Cannot read property ‘_c’ of undefined== 这是一...
包的安装
包的分类: 包的安装方式: 1. yum 安装 不需要手动解决依赖关系 本地yum源配置:不需要网络 网络源配置 yum : 2. 源码安装 2.1 安装准备: 2.2 分析安装平台环境 查看安装平台参数,下载合适的包 2.3 下载源码包 根据查到的参数下载源码包,建议下载到/usr/local/src目录下 2.4 安装源码包 示例: 此处以apache http示例:https://mirro...
