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

文章目录


前言

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


一、中介者模式

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

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

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

二、发布订阅模式

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

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

三、备忘录模式

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

  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.niftyadmin.cn/n/5799878.html

相关文章

.NET Framework 逐渐过时,.NET 8和 .NET 9引领未来

随着科技的不断进步&#xff0c;软件开发的工具和技术也在不断更新换代。.NET Framework&#xff0c;曾是微软的旗舰开发平台&#xff0c;曾经在软件开发领域占据了主导地位。然而&#xff0c;随着技术的演变和开发需求的变化&#xff0c;.NET Framework逐渐显得力不从心&#…

Hugging Face PEFT LoRA微调glm4-9b-chat

&#x1f60d;Hugging Face 的 PEFT 包微调 安装 PEFT包导包 from datasets import Dataset, load_dataset from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer训练数据集准备 训练集格式&#xff1a; js…

数据结构之线性表之顺序表

定义&#xff1a; 由n&#xff08;n>0&#xff09;个数据特性相同的元素构成的有限序列称为线性表 简单来说n个相同数据类型的数据组wsw合在一起的这么一个集合就是一个线性表 线性表包括顺序表和链表 1. 顺序表&#xff08;我们所有的代码实现都用函数来封装&#xff09…

【C++ 基础】类讲解

类 类 C的核心概念 struct myStu {int id;char name[128]; };类&#xff1a; 1、脑海中呈现代码需要表现的实体&#xff1b; 2、在这些实体中去寻找具有共性数据&#xff0c;共性行为&#xff1b; 3、创建类&#xff1b;通过类再去创建对象 梨树 栅栏 房子 桥梁 假山 实体&…

记一次 Android 高内存排查

本文未使用Android Studio内置Profile分析工具&#xff0c;其一是因为使用时AS明显卡顿一不小心就会无响应&#xff0c;其二部分应用无法连接AS 抓取日志&#xff0c;因此采取最原始但是最万能的方式 adb 抓取 MAT分析 文章目录 1 查看内存情况 adb shell dumpsys meminfo com…

基于C#实现的(WinForm)模拟操作系统文件管理系统

基于C# Framework实现的模拟操作系统文件管理系统&#xff08;文件管理大作业&#xff09; 1. 使用说明 1.1 项目简介 文件(File)是操作系统中的一个重要概念。在系统运行时&#xff0c;计算机以进程为基本单位进行资源的调度和分配&#xff1b;而在用户进行的输入、输出中&…

谷歌开发者工具 -来源/源码篇

Chrome DevTools - Sources来源/源码篇 一、官网二、主要用途三、来源篇1.查看文件&#xff08;1&#xff09;网页标签&#xff0c;查看所有加载资源&#xff08;2&#xff09;网页标签页得组织方式&#xff08;3&#xff09;查看文件内容 2.编辑 CSS 和 JavaScript3.创建、保存…

Y3编辑器教程8:资源管理器与存档、防作弊设置

文章目录 一、资源管理器简介1.1 界面介绍1.2 资源商店1.3 AI专区1.3.1 AI文生图1.3.2 AI图生图1.3.3 立绘头像 二、导入导出2.1 文件格式2.2 模型导入2.2.1 模型制作后导出2.2.2 模型文件导入Y3编辑器2.2.3 Y3编辑器角色、装饰物模型要求 2.3 纹理导入2.4 材质贴图2.4.1 材质支…