设计模式の中介者发布订阅备忘录模式
文章目录
- 前言
- 一、中介者模式
- 二、发布订阅模式
- 三、备忘录模式
前言
本篇是关于设计模式中介者模式、观察者(发布-订阅)模式、以及备忘录模式的学习笔记。
一、中介者模式
中介者模式是一种行为型设计模式,其核心目的是为了减少对象之间的复杂依赖关系,将多个对象之间的交互逻辑封装到一个中介者对象中,对象与对象之间不必直接发生关联,就如同租房找中介机构,由中介机构负责协调房东。
中介者模式一般包含以下的角色:
- 抽象中介者类/接口:通常定义了向同事类发送消息,注册同事类的方法。
- 中介者的具体实现:通常维护一个同事类的集合,并且拥有接收同事类消息并进行处理的方法。
- 抽象同事类/接口:持有一个中介者类的实例,并且定义了向中介者类发送信息,接受中介者类指令的方法模板。
- 同事类的具体实现:具体编写抽象同事类相关方法的逻辑。
下面举一个生活中的案例,例如航空系统的运作,飞机的起落时间,航线,高度,都是由塔台去统一调度(中介者),而飞机(同事类)和飞机之间一般是不会直接进行通信的。
定义一个抽象中介者类,拥有:
- 注册同事类的方法
- 接受同事类的消息的方法
/*** 抽象中介者*/
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…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
