设计模式の中介者发布订阅备忘录模式
文章目录
- 前言
- 一、中介者模式
- 二、发布订阅模式
- 三、备忘录模式
前言
本篇是关于设计模式中介者模式、观察者(发布-订阅)模式、以及备忘录模式的学习笔记。
一、中介者模式
中介者模式是一种行为型设计模式
,其核心目的是为了减少对象之间的复杂依赖关系,将多个对象之间的交互逻辑封装到一个中介者对象中,对象与对象之间不必直接发生关联,就如同租房找中介机构,由中介机构负责协调房东。
中介者模式一般包含以下的角色:
- 抽象中介者类/接口:通常定义了向同事类发送消息,注册同事类的方法。
- 中介者的具体实现:通常维护一个同事类的集合,并且拥有接收同事类消息并进行处理的方法。
- 抽象同事类/接口:持有一个中介者类的实例,并且定义了向中介者类发送信息,接受中介者类指令的方法模板。
- 同事类的具体实现:具体编写抽象同事类相关方法的逻辑。
下面举一个生活中的案例,例如航空系统的运作,飞机的起落时间,航线,高度,都是由塔台
去统一调度(中介者)
,而飞机(同事类)
和飞机之间一般是不会直接进行通信的。
定义一个抽象中介者类,拥有:
- 注册同事类的方法
- 接受同事类的消息的方法
/*** 抽象中介者*/
public interface AirTrafficControl {void notify(String message,Airplane airplane);void registerAirplane(Airplane airplane);
}
抽象同事类:
- 持有中介者对象
- 向中介者发送消息
- 接受中介者的消息
/*** 抽象同事类(持有中介者对象)*/
public abstract class Airplane {/*** 持有一个中介者对象*/AirTrafficControl airTrafficControl;String id;public Airplane(AirTrafficControl controlTower, String id) {this.airTrafficControl = controlTower;this.id = id;}/*** 向中介者发送消息* @param message*/public abstract void sendMessage(String message);/*** 接受中介者的指令(各个同事之间不发生联系,接受中介者的统一调度)* @param message*/public abstract void receiveMessage(String message);}
具体同事类:
- 会将自己注册到中介者的同事类集合中
- 向中介者发送消息
- 接受中介者的指令(各个同事之间不发生联系,接受中介者的统一调度)
/*** 持有中介者对象*/
public class ConcreteAirplane extends Airplane{public ConcreteAirplane(AirTrafficControl controlTower, String id) {super(controlTower, id);//将自己注册到中介者的同事类集合中airTrafficControl.registerAirplane(this);}/*** 向中介者发送消息** @param message*/@Overridepublic void sendMessage(String message) {System.out.println("飞机 " + id + " 发送消息: " + message);airTrafficControl.notify(message,this);}/*** 接受中介者的指令(各个同事之间不发生联系,接受中介者的统一调度)** @param message*/@Overridepublic void receiveMessage(String message) {System.out.println("飞机 " + id + " 收到消息: " + message);}
}
具体中介者类:
- 接受同事类的信息
- 向同事类发送消息
- 维护一个同事类的集合
/*** 具体中介者*/
public class ControlTower implements AirTrafficControl{/*** 维护一个同事类的集合*/List<Airplane> airplaneList = new ArrayList<>();/*** 接受同事类的信息* @param message* @param airplane*/@Overridepublic void notify(String message, Airplane airplane) {for (Airplane a : airplaneList) {if (a != airplane){a.receiveMessage(message);}}}@Overridepublic void registerAirplane(Airplane airplane) {airplaneList.add(airplane);}
}
客户端
public class Client {public static void main(String[] args) {ControlTower controlTower = new ControlTower();//创建具体的同事类ConcreteAirplane a101 = new ConcreteAirplane(controlTower, "A101");ConcreteAirplane b202 = new ConcreteAirplane(controlTower, "B202");ConcreteAirplane c303 = new ConcreteAirplane(controlTower, "C303");a101.sendMessage("准备降落,请保持跑道空闲。");b202.sendMessage("正在滑行至停机坪。");}
}
飞机 A101 发送消息: 准备降落,请保持跑道空闲。
飞机 B202 收到消息: 准备降落,请保持跑道空闲。
飞机 C303 收到消息: 准备降落,请保持跑道空闲。
飞机 B202 发送消息: 正在滑行至停机坪。
飞机 A101 收到消息: 正在滑行至停机坪。
飞机 C303 收到消息: 正在滑行至停机坪。
从上面的案例可以看出,中介者模式
的核心就在于建立中介者和同事类之间的联系,而各个同事类之间不互相联系。并且同事类需要持有中介者的对象,中介者也需要维护所有同事类对象的集合。
这样做的好处在于,修改交互逻辑只需更改中介者,不影响同事类,并且避免了对象之间复杂的网状引用。弊端在于,业务逻辑集中在了中介者类中,并且过于依赖中介者,如果中介者故障,系统交互可能中断。
二、发布订阅模式
发布订阅模式是一种消息传递模式
,在这种模式中,消息的发送者(发布者) 和 消息的接收者(订阅者) 之间没有直接联系。取而代之的是,一个中间“事件总线”
或“消息代理”
负责协调消息的传递。发布者将消息发送到总线,总线根据订阅规则将消息推送给感兴趣的订阅者。
主要包含以下的角色:
- 事件总线: 负责管理订阅者和消息的传递。
- 发布者: 产生事件并发布到总线上。
- 订阅者: 注册到总线上,接收感兴趣的事件。
例如现在有一个气象信息管理中台,xx网站需要从该平台获取最新的天气信息。(气象信息管理中台主动推送),传统方式如下,中台需要在自己的代码中维护所有订阅自己系统的数据,并逐个发送通知:
/*** 天气数据系统*/
public class WeatherDataSystem {private double temperature;private double pressure;private double humidity;private Sina sina;public WeatherDataSystem(Sina sina){this.sina = sina;}public double getTemperature() {return temperature;}public double getPressure() {return pressure;}public double getHumidity() {return humidity;}public void refreshData(double temperature, double pressure, double humidity){this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;pushData();}private void pushData(){sina.update(getHumidity(),getTemperature(),getPressure());}
}
public class Sina {private double temperature;private double pressure;private double humidity;public void update(double humidity, double temperature, double pressure){this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;show();}private void show(){System.out.println("目前温度:"+temperature);System.out.println("目前气压:"+pressure);System.out.println("目前湿度:"+humidity);}
}
这样做的弊端也是显而易见的,如果后续有其他网站接入中台?则需要不断地修改中台的代码,将新的平台注册进来,并且推送渠道也要增加新的平台。
使用发布-订阅模式
进行改造,则可以抽取一个发布者接口
、监听者接口
:
public interface Subject {/*** 注册订阅者* @param o*/void registerObservers(Observer o);/*** 移除订阅者* @param o*/void removeObservers(Observer o);/*** 通知所有订阅者*/void notifyObserver();
}
/*** 监听者接口* 定义监听者共有的行为*/
public abstract class Observer {private Subject subject;public Observer(Subject subject) {this.subject = subject;}abstract void update(double temperature, double pressure, double humidity);
}
信息的发布者(用户中台)实现发布者接口:
/*** 气象数据系统*/
public class WeatherData implements Subject{private double temperature;private double pressure;private double humidity;private List<Observer> observers;public WeatherData() {this.observers = new ArrayList<>();}/*** 注册订阅者** @param o*/@Overridepublic void registerObservers(Observer o) {observers.add(o);}/*** 移除订阅者** @param o*/@Overridepublic void removeObservers(Observer o) {observers.remove(o);}/*** 通知所有订阅者*/@Overridepublic void notifyObserver() {for (Observer observer : observers) {//调用监听者共有的行为observer.update(temperature,pressure,humidity);}}public void refreshData(double temperature, double pressure, double humidity){this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;notifyObserver();}
}
具体的网站作为接收者,可以将自身注册到发布者上:
/*** 订阅者*/
public class Baidu extends Observer {private double temperature;private double pressure;private double humidity;public Baidu(Subject subject) {super(subject);subject.registerObservers(this);}@Overridepublic void update(double temperature, double pressure, double humidity) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;show();}public void show(){System.out.println("百度天气:目前气温"+temperature);System.out.println("百度天气:目前气压"+pressure);System.out.println("百度天气:目前湿度"+humidity);}
}
客户端:
public class Client {public static void main(String[] args) {WeatherData weatherData = new WeatherData();new Baidu(weatherData);weatherData.refreshData(33.2,100,22.5);}
}
百度天气:目前气温33.2
百度天气:目前气压100.0
百度天气:目前湿度22.5
使用发布-订阅模式
改造后的代码,如果后续有新的订阅者去订阅中台,则中台的代码不需要修改,新的订阅者只需要将自身注册到中台即可。如果取消订阅也一样。
这样做的好处在于,发布者和订阅者无需直接通信,解除了耦合,并且可以动态增加或移除订阅者和主题。常见的消息队列(Kafka、RabbitMQ)中间件,就是该设计模式的体现。
三、备忘录模式
备忘录模式是一种行为型设计模式
,核心思想在于,不破坏封装的前提下,保存和恢复对象的状态。通过保存对象状态的快照,备忘录模式允许用户在需要时将对象恢复到之前的状态。备忘录模式常用于需要“撤销”和“恢复”功能的场景,例如文本编辑器、游戏存档等。
通常会包含以下角色:
- 备忘录: 用于存储发起者对象的内部状态。
- 发起者: 创建备忘录并可以从中恢复其状态。
- 管理者: 负责保存和管理备忘录,但不会直接操作或修改备忘录的内容。
举一个生活中的案例,例如游戏的存档功能,我们首先创建一个对象代表游戏角色
,在该对象中,除了基本的属性,还定义了
- 创建备忘录对象,传递自身某一时刻的状态。
- 从备忘录中获取状态,并赋值给属性。
public class Role {/*** 攻击力*/private String attackPower;/*** 防御力*/private String defenseCapability;/*** 版本*/private int version;public Role() {}public Role(String attackPower, String defenseCapability, int version) {this.attackPower = attackPower;this.defenseCapability = defenseCapability;this.version = version;}/*** 设置* @param attackPower*/public void setAttackPower(String attackPower) {this.attackPower = attackPower;}/*** 设置* @param defenseCapability*/public void setDefenseCapability(String defenseCapability) {this.defenseCapability = defenseCapability;}/*** 设置* @param version*/public void setVersion(int version) {this.version = version;}public String getAttackPower() {return attackPower;}public String getDefenseCapability() {return defenseCapability;}public int getVersion() {return version;}/*** 创建备忘录* @return*/public Memo createMemo(){return new Memo(attackPower,defenseCapability,version);}/*** 回退到指定的版本* @param memo*/public void getMemo(Memo memo){this.attackPower = memo.getAttackPower();this.defenseCapability = memo.getDefenseCapability();this.version = memo.getVersion();}}
备忘录对象,是游戏角色
某一时刻的快照,包含了游戏角色
的属性。
public class Memo {/*** 攻击力*/private String attackPower;/*** 防御力*/private String defenseCapability;/*** 版本*/private int version;public Memo() {}public Memo(String attackPower, String defenseCapability, int version) {this.attackPower = attackPower;this.defenseCapability = defenseCapability;this.version = version;}public String getAttackPower() {return attackPower;}public String getDefenseCapability() {return defenseCapability;}public int getVersion() {return version;}
}
备忘录对象的统一管理类,因为存档可能有多份,所以使用一个集合进行管理
public class Caretaker {private List<Memo> memoList = new ArrayList<>();public void add(Memo memo){memoList.add(memo);}public Memo rollback(int reversion){return memoList.get(reversion);}
}
客户端:
public class Client {public static void main(String[] args) {Caretaker caretaker = new Caretaker();Role role = new Role("1w","5k",0);System.out.println("初始状态,角色攻击"+role.getAttackPower()+"角色防御"+role.getDefenseCapability());caretaker.add(role.createMemo());role.setAttackPower("8k");role.setDefenseCapability("4.5k");role.setVersion(1);System.out.println("第一次大战结束,角色攻击"+role.getAttackPower()+"角色防御"+role.getDefenseCapability());caretaker.add(role.createMemo());role.setAttackPower("1k");role.setDefenseCapability("0.5k");role.setVersion(2);System.out.println("第二次大战结束,角色攻击"+role.getAttackPower()+"角色防御"+role.getDefenseCapability());caretaker.add(role.createMemo());Memo rollback = caretaker.rollback(0);System.out.println("回溯到初始状态,角色攻击"+rollback.getAttackPower()+"角色防御"+rollback.getDefenseCapability());}
}
初始状态,角色攻击1w角色防御5k
第一次大战结束,角色攻击8k角色防御4.5k
第二次大战结束,角色攻击1k角色防御0.5k
回溯到初始状态,角色攻击1w角色防御5k
这样做的好处在于, 备忘录存储状态的细节完全对管理者隐藏,并且可以维护多个备忘录以实现多级撤销。通常适用于需要撤销/恢复功能,以及对象状态需要存档的场景。
相关文章:
设计模式の中介者发布订阅备忘录模式
文章目录 前言一、中介者模式二、发布订阅模式三、备忘录模式 前言 本篇是关于设计模式中介者模式、观察者(发布-订阅)模式、以及备忘录模式的学习笔记。 一、中介者模式 中介者模式是一种行为型设计模式,其核心目的是为了减少对象之间的复杂…...

云手机群控能用来做什么?
随着云手机的发展,云手机群控技术逐渐从小众的游戏多开工具,发展为涵盖多个领域的智能操作平台。不论是手游搬砖、短视频运营,还是账号养成等场景,云手机群控都展现出了强大的应用潜力。本文将为大家详细解析云手机群控的应用场景…...

fpgafor循环语句使用
genvar i;//循环变量名称 generate for(i0;i<4;ii1)begin:tx//自己定义名称 //循环内容 end endgenerate12位的16进制乘以4就是48位位宽的2进制 因为 222*2(2^4)16...
【FastAPI】BaseHTTPMiddleware类
一、概述 在FastAPI中,BaseHTTPMiddleware 类是Starlette框架提供的一个抽象基类,它允许开发者基于HTTP请求/响应接口编写ASGI中间件。 这个类对于希望实现自定义中间件逻辑的开发者来说是非常重要的工具。 通过继承 BaseHTTPMiddleware 并实现特定的方…...

Solon v3.0.5 发布!(Spring 可以退休了吗?)
Solon 框架! 新一代,面向全场景的 Java 应用开发框架。从零开始构建(非 java-ee 架构),有灵活的接口规范与开放生态。 追求: 更快、更小、更简单提倡: 克制、高效、开放、生态 有什么特点&am…...
网络安全攻防演练中的常见计策
大家觉得有意义记得关注和点赞!!! 引言 在网络安全攻防演练里面,用于分析攻击者动机和行为的,国外的有基于攻击链分析的模型(如Cyber Kill Chain和ATT&CK)和基于威胁行为的模型(…...

SD卡模块布局布线设计
1、SD/TF/SIM卡的定义 2、SD/TF/SIM卡模块引脚定义以及图示 3、SD/TF/SIM卡接口布局和布线 4、小结 1、BGA两线交叉时,可以在源头将两线互相短路连接,然后再输出口删除一小节线,然后CHRLX/V,这样就可以换两条线的网络,…...
Flask-----SQLAlchemy教程
存session session[username] username # 存储数据到 session 取session username session.get(username) render_template return render_template(index.html, usernameAlice),渲染一个包含 username 变量的模板。 redirect return redirect(url_for(profil…...
STM32 高级 物联网通信之CAN通讯
目录 CAN通讯介绍 物理层 协议层 CAN的帧(报文)种类 1 数据帧(发送单元->接受单元) 2 远程帧(接受单元->发送单元) 3 错误帧(发送方发送数据错误会发送的状态帧) 4 过载帧(接收方放不下会发送到的状态帧) 5 帧间隔(状态) 数据帧介绍 远程帧介绍 C…...

“乡村探索者”:村旅游网站的移动应用开发
3.1 可行性分析 从三个不同的角度来分析,确保开发成功的前提是有可行性分析,只有进行提前分析,符合程序开发流程才不至于开发过程的中断。 3.1.1 技术可行性 在技术实现层次,分析了好几种技术实现方法,并且都有对应的成…...

前端案例---自定义鼠标右键菜单
之前右击出现默认的选项菜单,使用evt.preventDefault()把默认的去掉,然后自定义右击的样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible"…...
浅谈归一化
在深度学习中,对网络层进行归一化(Normalization,简称Norm)是一个重要的技巧。常见的归一化方法包括批归一化(Batch Normalization)、层归一化(Layer Normalization)、实例归一化&am…...
lodash常用函数
文章目录 一、数组1、chunk分组2、difference、differenceBy、differenceWith3、findIndex4、intersection、intersectionBy、intersectionWith5、union、unionBy、unionWith 二、对象1、pick、omit 2、get、set三、数学1、sum、sumBy2、range 四、工具函数1、isEqual、isEmpty…...
触控算法总结
一、触控湿手指算法的具体实现原理涉及多个方面的技术和方法,主要包括以下几个关键点 1.电容变化检测 电容式触摸屏通过检测电容变化来确定触摸位置。当手指接触屏幕时,会引起电容的变化。然而,当手指湿润时,水分会影响电容值,导致触摸屏误判成无法正确识别触控点 2.噪声过滤: …...
齐次矩阵包含平移和旋转
第一个矩阵旋转矩阵 A [ R 1 0 0 1 ] A\left[\begin{matrix}R_{1} & 0\\0 & 1\end{matrix}\right] A[R1001] 第一个平移矩阵 B [ 1 T 1 0 1 ] B\left[\begin{matrix}1 & T_{1}\\0 & 1\end{matrix}\right] B[10T11] C [ R 2 0 0 1 ] C\left[\be…...

Move AI技术浅析(四):运动跟踪与估计
一、运动跟踪与估计模块概述 运动跟踪与估计 是 Move AI 的核心模块之一,其主要任务是从提取到的关键点特征中,分析和理解运动的动态特性,包括运动轨迹、速度、加速度、方向等。该模块通常包括 时间序列分析 和 运动估计 两个子模块。 时间…...

NCR+可变电荷块3——NCB/cell绘图1
文献method参考: 蛋白质序列数据从uniprot中获取 https://www.uniprot.org/uniprotkb/P46013/entry https://www.uniprot.org/uniprotkb/P06748/entry、 1,电荷分布计算: Charge distribution was calculated as the sum of the charges …...
数据仓库是什么?数据仓库简介
数据仓库(Data Warehouse)是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持企业的管理决策。以下是对数据仓库的详细解释: 一、定义与特性 定义:数据仓库是构建在组织的现有数据基础上&#x…...

AI的进阶之路:从机器学习到深度学习的演变(二)
AI的进阶之路:从机器学习到深度学习的演变(一) 三、机器学习(ML):AI的核心驱动力 3.1 机器学习的核心原理 机器学习(Machine Learning, ML)突破了传统编程的局限,它不再…...
C++中属性(Attributes)
属性(Attributes)在 C 中的完整讲解 在 C 中,属性(Attributes) 是一种编译时机制,用于附加元数据到函数、变量、类型等元素上,以指导编译器如何优化、检查、警告或者改变编译行为。通过属性&am…...
关于ETL的BackgroundScheduler同步方案和misfire_grace_time
如果做ETL避免脏数据,那么不可以允许同一个job有并行允许的情况,也就是说max_instance参数始终设置成1。 这时候执行ETL任务,会有以下情况。 1 任务不超时。正常执行 2 任务超时。如果下一个时间点上一次任务还没有执行完,那么…...

网页前端开发(基础进阶4--axios)
Ajax Ajax(异步的JavaScript和XML) 。 XML是可扩展标记语言,本质上是一种数据格式,可以用来存储复杂的数据结构。 可以通过Ajax给服务器发送请求,并获取服务器响应的数据。 Ajax采用异步交互:可以在不重新加载整个页面的情况下&am…...
32单片机——窗口看门狗
1、WWDG的简介 WWDG:Window watchdog,即窗口看门狗 窗口看门狗本质上是能产生系统复位信号和提前唤醒中断的递减计数器 WWDG产生复位信号的条件: (1)当递减计数器值从0x40减到0x3F时复位(即T6位跳变到0&a…...
【前端】js如何处理计算精度问题
JavaScript 的精度问题源于其遵循 IEEE 754 标准的 64 位双精度浮点数表示法,导致 0.1 0.2 ! 0.3 等经典问题。以下是系统化的解决方案及适用场景: ⚙️ 一、整数转换法(适合简单运算) 将小数转换为整数运算后再还原࿰…...

软件测评服务如何依据标准确保品质?涵盖哪些常见内容?
软件测评服务涉及对软件的功能和性能等多维度进行评估和检验,这一过程有助于确保软件的品质,降低故障发生率及维护费用,对于软件开发和维护环节具有至关重要的价值。 测评标准依据 GB/T 25000.51 - 2016是软件测评的核心依据。依照这一标准…...
SpringBoot+Mybatisplus配置多数据源(超级简单!!!!)
今天分享配置多数据源的另外一种方式,SpringBoMybatisplus配置多数据源,此种方式配置相对简单,都是苞米豆封装好的,配置容易;此篇分享比较简单的方式配置数据源,多个固定的数据源,通过注解选择使…...

第1章_数据分析认知_知识点笔记
来自:数据分析自学课程-戴戴戴师兄 逐字稿:【课程4.0】第1章_分析认知_知识点笔记 【课程4.0】第1章 分析认知 知识点总结 数据分析的核心价值不是工具,而是用数据驱动业务增长。 一、数据分析的本质认知 数据分析是什么? 不是酷…...

Python训练第四十六天
DAY 46 通道注意力(SE注意力) 知识点回顾: 不同CNN层的特征图:不同通道的特征图什么是注意力:注意力家族,类似于动物园,都是不同的模块,好不好试了才知道。通道注意力:模型的定义和插入的位置通…...
C++编程——关于比较器的使用
注: 简单记录一下C里比较器的构建,常用于自定义 sort() 函数和优先队列的改写优先级。 简单构建比较器: sort() 函数: vector<int> arr;//(a, b) -> true : a < b //升序排列 bool compare(int a, int b) {retur…...

深入了解linux系统—— 进程池
前言: 本篇博客所涉及到的代码以同步到本人gitee:进程池 迟来的grown/linux - 码云 - 开源中国 一、池化技术 在之前的学习中,多多少少都听说过池,例如内存池,线程池等等。 那这些池到底是干什么的呢?池…...