前言
本篇是关于设计模式中介者模式、观察者(发布-订阅)模式、以及备忘录模式的学习笔记。
一、中介者模式
中介者模式是一种行为型设计模式
,其核心目的是为了减少对象之间的复杂依赖关系,将多个对象之间的交互逻辑封装到一个中介者对象中,对象与对象之间不必直接发生关联,就如同租房找中介机构,由中介机构负责协调房东。
中介者模式一般包含以下的角色:
- 抽象中介者类/接口:通常定义了向同事类发送消息,注册同事类的方法。
- 中介者的具体实现:通常维护一个同事类的集合,并且拥有接收同事类消息并进行处理的方法。
- 抽象同事类/接口:持有一个中介者类的实例,并且定义了向中介者类发送信息,接受中介者类指令的方法模板。
- 同事类的具体实现:具体编写抽象同事类相关方法的逻辑。
下面举一个生活中的案例,例如航空系统的运作,飞机的起落时间,航线,高度,都是由塔台
去统一调度(中介者)
,而飞机(同事类)
和飞机之间一般是不会直接进行通信的。
定义一个抽象中介者类,拥有:
- 注册同事类的方法
- 接受同事类的消息的方法
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
*/
@Override
public void sendMessage(String message) {
System.out.println("飞机 " + id + " 发送消息: " + message);
airTrafficControl.notify(message,this);
}
/**
* 接受中介者的指令(各个同事之间不发生联系,接受中介者的统一调度)
*
* @param message
*/
@Override
public void receiveMessage(String message) {
System.out.println("飞机 " + id + " 收到消息: " + message);
}
}
具体中介者类:
- 接受同事类的信息
- 向同事类发送消息
- 维护一个同事类的集合
java">/**
* 具体中介者
*/
public class ControlTower implements AirTrafficControl{
/**
* 维护一个同事类的集合
*/
List<Airplane> airplaneList = new ArrayList<>();
/**
* 接受同事类的信息
* @param message
* @param airplane
*/
@Override
public void notify(String message, Airplane airplane) {
for (Airplane a : airplaneList) {
if (a != airplane){
a.receiveMessage(message);
}
}
}
@Override
public 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 收到消息: 正在滑行至停机坪。
从上面的案例可以看出,中介者模式
的核心就在于建立中介者和同事类之间的联系,而各个同事类之间不互相联系。并且同事类需要持有中介者的对象,中介者也需要维护所有同事类对象的集合。
这样做的好处在于,修改交互逻辑只需更改中介者,不影响同事类,并且避免了对象之间复杂的网状引用。弊端在于,业务逻辑集中在了中介者类中,并且过于依赖中介者,如果中介者故障,系统交互可能中断。
二、发布订阅模式
发布订阅模式是一种消息传递模式
,在这种模式中,消息的发送者(发布者) 和 消息的接收者(订阅者) 之间没有直接联系。取而代之的是,一个中间“事件总线”
或“消息代理”
负责协调消息的传递。发布者将消息发送到总线,总线根据订阅规则将消息推送给感兴趣的订阅者。
主要包含以下的角色:
- 事件总线: 负责管理订阅者和消息的传递。
- 发布者: 产生事件并发布到总线上。
- 订阅者: 注册到总线上,接收感兴趣的事件。
例如现在有一个气象信息管理中台,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
*/
@Override
public void registerObservers(Observer o) {
observers.add(o);
}
/**
* 移除订阅者
*
* @param o
*/
@Override
public void removeObservers(Observer o) {
observers.remove(o);
}
/**
* 通知所有订阅者
*/
@Override
public 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);
}
@Override
public 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)中间件,就是该设计模式的体现。
三、备忘录模式
备忘录模式是一种行为型设计模式
,核心思想在于,不破坏封装的前提下,保存和恢复对象的状态。通过保存对象状态的快照,备忘录模式允许用户在需要时将对象恢复到之前的状态。备忘录模式常用于需要“撤销”和“恢复”功能的场景,例如文本编辑器、游戏存档等。
通常会包含以下角色:
- 备忘录: 用于存储发起者对象的内部状态。
- 发起者: 创建备忘录并可以从中恢复其状态。
- 管理者: 负责保存和管理备忘录,但不会直接操作或修改备忘录的内容。
举一个生活中的案例,例如游戏的存档功能,我们首先创建一个对象代表游戏角色
,在该对象中,除了基本的属性,还定义了
- 创建备忘录对象,传递自身某一时刻的状态。
- 从备忘录中获取状态,并赋值给属性。
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
这样做的好处在于, 备忘录存储状态的细节完全对管理者隐藏,并且可以维护多个备忘录以实现多级撤销。通常适用于需要撤销/恢复功能,以及对象状态需要存档的场景。