设计模式の中介者发布订阅备忘录模式

ops/2024/12/26 11:54:50/

文章目录


前言

  本篇是关于设计模式中介者模式、观察者(发布-订阅)模式、以及备忘录模式的学习笔记。


一、中介者模式

  中介者模式是一种行为型设计模式,其核心目的是为了减少对象之间的复杂依赖关系,将多个对象之间的交互逻辑封装到一个中介者对象中,对象与对象之间不必直接发生关联,就如同租房找中介机构,由中介机构负责协调房东。
  中介者模式一般包含以下的角色:

  1. 抽象中介者类/接口:通常定义了向同事类发送消息,注册同事类的方法。
  2. 中介者的具体实现:通常维护一个同事类的集合,并且拥有接收同事类消息并进行处理的方法。
  3. 抽象同事类/接口:持有一个中介者类的实例,并且定义了向中介者类发送信息,接受中介者类指令的方法模板。
  4. 同事类的具体实现:具体编写抽象同事类相关方法的逻辑。

  下面举一个生活中的案例,例如航空系统的运作,飞机的起落时间,航线,高度,都是由塔台去统一调度(中介者),而飞机(同事类)和飞机之间一般是不会直接进行通信的。
  定义一个抽象中介者类,拥有:

  • 注册同事类的方法
  • 接受同事类的消息的方法
java">/*** 抽象中介者*/
public interface AirTrafficControl {void notify(String message,Airplane airplane);void registerAirplane(Airplane airplane);
}

  抽象同事类:

  • 持有中介者对象
  • 向中介者发送消息
  • 接受中介者的消息
java">/*** 抽象同事类(持有中介者对象)*/
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);}

  具体同事类:

  • 会将自己注册到中介者的同事类集合中
  • 向中介者发送消息
  • 接受中介者的指令(各个同事之间不发生联系,接受中介者的统一调度)
java">/*** 持有中介者对象*/
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);}
}

  具体中介者类:

  • 接受同事类的信息
  • 向同事类发送消息
  • 维护一个同事类的集合
java">/*** 具体中介者*/
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);}
}

  客户端

java">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 收到消息: 正在滑行至停机坪。

  从上面的案例可以看出,中介者模式的核心就在于建立中介者和同事类之间的联系,而各个同事类之间不互相联系。并且同事类需要持有中介者的对象,中介者也需要维护所有同事类对象的集合。
  这样做的好处在于,修改交互逻辑只需更改中介者,不影响同事类,并且避免了对象之间复杂的网状引用。弊端在于,业务逻辑集中在了中介者类中,并且过于依赖中介者,如果中介者故障,系统交互可能中断。

二、发布订阅模式

  发布订阅模式是一种消息传递模式,在这种模式中,消息的发送者(发布者) 和 消息的接收者(订阅者) 之间没有直接联系。取而代之的是,一个中间“事件总线”“消息代理”负责协调消息的传递。发布者将消息发送到总线,总线根据订阅规则将消息推送给感兴趣的订阅者。
  主要包含以下的角色:

  1. 事件总线: 负责管理订阅者和消息的传递。
  2. 发布者: 产生事件并发布到总线上。
  3. 订阅者: 注册到总线上,接收感兴趣的事件。

  例如现在有一个气象信息管理中台,xx网站需要从该平台获取最新的天气信息。(气象信息管理中台主动推送),传统方式如下,中台需要在自己的代码中维护所有订阅自己系统的数据,并逐个发送通知:

java">/*** 天气数据系统*/
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());}
}
java">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);}
}

  这样做的弊端也是显而易见的,如果后续有其他网站接入中台?则需要不断地修改中台的代码,将新的平台注册进来,并且推送渠道也要增加新的平台。
  使用发布-订阅模式进行改造,则可以抽取一个发布者接口监听者接口:

java">public interface Subject {/*** 注册订阅者* @param o*/void registerObservers(Observer o);/*** 移除订阅者* @param o*/void removeObservers(Observer o);/*** 通知所有订阅者*/void notifyObserver();
}
java">/*** 监听者接口* 定义监听者共有的行为*/
public abstract class Observer {private Subject subject;public Observer(Subject subject) {this.subject = subject;}abstract void update(double temperature, double pressure, double humidity);
}

  信息的发布者(用户中台)实现发布者接口:

java">/*** 气象数据系统*/
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();}
}

  具体的网站作为接收者,可以将自身注册到发布者上:

java">/*** 订阅者*/
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);}
}

  客户端:

java">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)中间件,就是该设计模式的体现。

三、备忘录模式

  备忘录模式是一种行为型设计模式,核心思想在于,不破坏封装的前提下,保存和恢复对象的状态。通过保存对象状态的快照,备忘录模式允许用户在需要时将对象恢复到之前的状态。备忘录模式常用于需要“撤销”和“恢复”功能的场景,例如文本编辑器、游戏存档等。
  通常会包含以下角色:

  1. 备忘录: 用于存储发起者对象的内部状态。
  2. 发起者: 创建备忘录并可以从中恢复其状态。
  3. 管理者: 负责保存和管理备忘录,但不会直接操作或修改备忘录的内容。

  举一个生活中的案例,例如游戏的存档功能,我们首先创建一个对象代表游戏角色,在该对象中,除了基本的属性,还定义了

  • 创建备忘录对象,传递自身某一时刻的状态。
  • 从备忘录中获取状态,并赋值给属性。
java">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();}}

  备忘录对象,是游戏角色某一时刻的快照,包含了游戏角色的属性。

java">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;}
}

  备忘录对象的统一管理类,因为存档可能有多份,所以使用一个集合进行管理

java">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);}
}

  客户端:

java">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

  这样做的好处在于, 备忘录存储状态的细节完全对管理者隐藏,并且可以维护多个备忘录以实现多级撤销。通常适用于需要撤销/恢复功能,以及对象状态需要存档的场景。



http://www.ppmy.cn/ops/145118.html

相关文章

ubuntu安装sublime安装与免费使用

1. ubuntu安装sublime 参考官网: Linux Package Manager Repositories 2. 破解过程 打开如下网址,打开/opt/sublime_text/sublime_text https://hexed.it/ 3. 替换在hexed打开的文件中查找并替换: 4180激活方法 使用二进制编辑器 8079 0500 0f94 c2替换为 c641 05…

十二、e2studio VS STM32CubeIDE之栈回溯cmbacktrace

目录 一、概述/目的 二、注意或限制 三、stm32u575 cmbacktrace 四、瑞萨ra6m4 cmbacktrace 五、总结 一、概述/目的 我们分享过十四、从0开始卷出一个新项目之瑞萨RZN2L之栈回溯 我们继续分享通用mcu cm33内核stm32u575和瑞萨ra6m4的栈回溯cmbacktrace的例程源码 为了快…

金仓数据库安装-Kingbase v9-centos

在很多年前有个项目用的金仓数据库&#xff0c;上线稳定后就没在这个项目了&#xff0c;只有公司的开发环境还在维护&#xff0c;已经好多年没有安装过了&#xff0c;重温一下金仓数据库安装&#xff0c;体验一下最新版本&#xff0c;也做一个新版本的试验环境&#xff1b; 一、…

echarts5.0以上版本不能使用4.x的map,解决办法

先把echarts 4版本的map文件夹放到项目中 然后在项目中使用 import china from “/utils/map/json/china.json”; import “/utils/map/js/china”; 我是放到utils下面了 在组件中使用的时候加上一行代码就可以了 echarts.registerMap(china, china);

Android笔记:解决fragment+viewpager第二次进入的时候没有数据的问题

在使用ViewPager结合Fragment时&#xff0c;如果第二次无法显示&#xff0c;可能是因为FragmentManager没有正确处理Fragment的状态&#xff0c;或者ViewPager的适配器没有正确处理Fragment的生命周期。 解决方法&#xff1a; 确保你的FragmentPagerAdapter或FragmentStatePa…

排序算法(系列)

希尔排序&#xff08;Shell Sort&#xff09;是一种插入排序的改进版本。它是基于插入排序的思想&#xff0c;通过将待排序的元素进行分组&#xff0c;然后对每组进行插入排序&#xff0c;逐步减少分组的大小&#xff0c;最终完成排序。希尔排序的核心思想是将序列分为多个子序…

赛博错题本

机构抽象老师非得让我们整一个错题本&#xff0c;我寻思都学计算机了&#xff0c;还在整高中做题呢一套是什么意思呢&#xff0c;更何况考试也就一周一次&#xff0c;你整个本完完全全没有必要&#xff0c;整个赛博错题本得了。以后错题都会存在这里&#xff0c;基本上一周一更…

C#中的属性索引器(Indexer)

属性索引器&#xff08;Indexer&#xff09;是C#中一个非常有用的特性&#xff0c;它允许类的实例像数组一样通过索引进行访问。索引器不仅限于整数索引&#xff0c;还可以使用其他类型&#xff0c;如字符串&#xff0c;作为索引键。这使得索引器在访问集合类型或需要通过键来访…