备忘录设计模式

本文深入探讨备忘录设计模式,一种行为型模式,允许在不破坏封装性的情况下保存和恢复对象状态。通过游戏存档场景举例,详细介绍了备忘录模式的实现方式,包括通过实例对象和文件进行状态备忘,以及Log4j框架中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

备忘录设计模式


1. 简单介绍

备忘录模式(Memento)是一种行为型设计模式,该模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

2. 使用场景
  • 需要保存一个对象在某一个时刻的状态或部分状态
  • 一个对象需要暴露自己的内部状态,而又不希望外界直接对其内部状态进行访问。此时外界可通过备忘录模式中的Caretaker来间接访问该对象的内部状态
3. 场景举例

当我们在玩单机游戏时,为了避免每次开机重头开始,可以将游戏当前状态进行存档,之后再次进入游戏,只需读取存档即可恢复到存档时的游戏状态。这便是备忘录模式的典型应用场景,即备忘某一时刻游戏(或实例对象)状态。对于单机游戏来说,游戏的状态需要备忘到硬盘上,以免关机丢失。

4. UML类图

备忘录模式UML类图

5. 具体实现

方式一:通过实例对象进行Originator进行状态备忘

描述

  • 背景:英雄联盟游戏中的艾希(Ashe)角色的状态保存与恢复
  • Ashe:英雄联盟游戏角色艾希,充当备忘录模式中的Originator角色
  • Memento:用于保存艾希的状态
  • LOLCaretaker:英雄联盟角色状态保存负责人,充当备忘录模式中的Caretaker角色
  • Client:经常崩溃的英雄联盟客户端

代码实现

Ashe.java

/**
 * 京东商城,充当ObjectStruct角色
 */
/**
 * 英雄联盟游戏角色艾希,充当备忘录模式中的Originator角色
 */
public class Ashe {

    /**
     * 血量,需要备忘保存
     */
    private Integer HP;

    /**
     * 蓝量,需要备忘保存
     */
    private Integer blueAmount;

    /**
     * 攻击速度,需要备忘保存
     */
    private Double attackSpeed;

    /**
     * 攻击力,需要备忘保存
     */
    private Integer attackPower;

    /**
     * 固定属性,不需要备忘保存
     */
    private String roleName = "Ashe";

    public Memento saveStatus() {
        return new Memento(HP, blueAmount, attackSpeed, attackPower);
    }

    public void restoreFrom(Memento memento) {
        this.HP = memento.getHP();
        this.blueAmount = memento.getBlueAmount();
        this.attackSpeed = memento.getAttackSpeed();
        this.attackPower = memento.getAttackPower();
    }

    // 省略Getter、Seeter和toString方法
}

Element.java

/**
 * Memento,用于保存艾希的状态
 */
public class Memento {

    /**
     * 血量
     */
    private Integer HP;

    /**
     * 蓝量
     */
    private Integer blueAmount;

    /**
     * 攻击速度
     */
    private Double attackSpeed;

    /**
     * 攻击力
     */
    private Integer attackPower;

    public Memento(Integer HP, Integer blueAmount, Double attackSpeed, Integer attackPower) {
        this.HP = HP;
        this.blueAmount = blueAmount;
        this.attackSpeed = attackSpeed;
        this.attackPower = attackPower;
    } 
    // 省略Getter、Seeter方法
}

LOLCaretaker.java

/**
 * 英雄联盟角色状态保存负责人,充当备忘录模式中的Caretaker角色
 */
public class LOLCaretaker {

    private static Map<String, Memento> roleMemento = new HashMap<>();

    public static void put(String roleName, Memento memento) {
        roleMemento.put(roleName, memento);
    }

    public static Memento get(String roleName) {
        return roleMemento.get(roleName);
    }
}

Client.java

/**
 * 经常崩溃的英雄联盟客户端
 */  
public class LOLClient {

    public static void main(String[] args) {

        // 欢迎来到召唤师,碾碎他们
        // 游戏开始,设置艾希的初始化状态
        Ashe ashe = new Ashe();
        ashe.setHP(642);
        ashe.setBlueAmount(600);
        ashe.setAttackPower(120);
        ashe.setAttackSpeed(1.2D);
        System.out.println(ashe.toString());

        // 进行备忘
        LOLCaretaker.put(ashe.getRoleName(), ashe.saveStatus());

        // 经过一轮SOLO后,艾希状态很差
        ashe.setHP(50);
        ashe.setBlueAmount(100);
        ashe.setAttackPower(120);
        ashe.setAttackSpeed(1.2D);
        System.out.println(ashe.toString());

        // 开挂,恢复初始状态
        ashe.restoreFrom(LOLCaretaker.get(ashe.getRoleName()));
        System.out.println(ashe.toString());
    }
}

方式二:通过文件对Originator进行状态备忘

描述

  • 背景:英雄联盟游戏中的艾希(Ashe)角色的状态保存与恢复,与方式以不同的是,本次通过文件的方式进行角色状态的保存。其他均和方式一相同

代码实现

Ashe.java

/**
 * 英雄联盟游戏角色艾希,充当备忘录模式中的Originator角色
 */
public class Ashe implements Serializable {

    /**
     * 血量,需要备忘保存
     */
    private Integer HP;

    /**
     * 蓝量,需要备忘保存
     */
    private Integer blueAmount;

    /**
     * 攻击速度,需要备忘保存
     */
    private Double attackSpeed;

    /**
     * 攻击力,需要备忘保存
     */
    private Integer attackPower;

    /**
     * 固定属性,不需要备忘保存
     */
    private String roleName = "Ashe";

    public Memento saveStatus() throws Exception {
        return new Memento(this);
    }

    public void restoreFrom(Memento memento) {
        this.HP = memento.getHP();
        this.blueAmount = memento.getBlueAmount();
        this.attackSpeed = memento.getAttackSpeed();
        this.attackPower = memento.getAttackPower();
    }

    // 省略Getter、Seeter和toString方法
}

Memento.java

/**
 * Memento,用于保存艾希的状态
 */
public class Memento implements Serializable {

    private static final String FILE_PATH = "D:\\ashe";

    /**
     * 血量
     */
    private Integer HP;

    /**
     * 蓝量
     */
    private Integer blueAmount;

    /**
     * 攻击速度
     */
    private Double attackSpeed;

    /**
     * 攻击力
     */
    private Integer attackPower;

    // 将需要备忘的状态保存在硬盘文件上
    public Memento(Ashe ashe) throws Exception {
        this.HP = ashe.getHP();
        this.blueAmount = ashe.getBlueAmount();
        this.attackSpeed = ashe.getAttackSpeed();
        this.attackPower = ashe.getAttackPower();

        File file = new File(FILE_PATH);
        if (!file.exists()) {
            file.createNewFile();
        }
        FileOutputStream fos = new FileOutputStream(file);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(this);
        oos.flush();
    }

    // 从硬盘文件上加载保存过的状态
    public static Memento loadFromFile() throws Exception {
        File file = new File(FILE_PATH);
        if (!file.exists()) {
            return null;
        }
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        return (Memento) ois.readObject();
    }

LOLCaretaker.java

/**
 * 英雄联盟角色状态保存负责人,充当备忘录模式中的Caretaker角色
 */
public class LOLCaretaker {

    private static Map<String, Memento> roleMemento = new HashMap<>();

    public static void put(String roleName, Memento memento) {
        roleMemento.put(roleName, memento);
    }

    public static Memento get(String roleName) {
        return roleMemento.get(roleName);
    }
}

Client.java

/**
 * 经常崩溃的英雄联盟客户端
 */  
public class LOLClient {

    public static void main(String[] args) {

        // 欢迎来到召唤师,碾碎他们
        // 游戏开始,设置艾希的初始化状态
        Ashe ashe = new Ashe();
        ashe.setHP(642);
        ashe.setBlueAmount(600);
        ashe.setAttackPower(120);
        ashe.setAttackSpeed(1.2D);
        System.out.println(ashe.toString());

        // 进行备忘
        LOLCaretaker.put(ashe.getRoleName(), ashe.saveStatus());

        // 经过一轮SOLO后,艾希状态很差
        ashe.setHP(50);
        ashe.setBlueAmount(100);
        ashe.setAttackPower(120);
        ashe.setAttackSpeed(1.2D);
        System.out.println(ashe.toString());

        // 开挂,恢复初始状态
        ashe.restoreFrom(LOLCaretaker.get(ashe.getRoleName()));
        System.out.println(ashe.toString());
    }
}
7. 源码展示

Log4j开源日志框架中使用了备忘录模式。与本文所讲解的标准的备忘录模式相比,Log4j中使用备忘录模式,显得更为简洁,却也能达到备忘的作用。Log4jLog4jLogEvent类,充当Originator角色,它在特定状态下需要对某些状态进行备忘。其实现方式是通过Builder模式,建造一个Log4jLogEvent实例(含有需要备忘的状态),即Memento实例。并通过AbstractJacksonLayout类对该实例进行打印操作。其中源码如下:

RingBufferLogEvent.java

/**
 * 对应备忘录模式中的Originator角色
 */
public class Log4jLogEvent implements LogEvent {
    /**
     * 需要进行备忘的状态值
     * /
    private final String loggerFqcn;
    private final Marker marker;
    private final Level level;
    private final String loggerName;
    private Message message;
    // ......
    
    /**
     * 创建备忘实例,其中LogEvent是Log4jLogEvent所实现的接口
     * 故Log4jLogEvent使用的备忘录模式,与标准的备忘录模式相比,没有Memento这一角色
     */
    public LogEvent createMemento() {
        return new Log4jLogEvent.Builder(this).build();
    }    

AbstractJacksonLayout.java

abstract class AbstractJacksonLayout extends AbstractStringLayout {
    /**
     * 当event是LogEvent实例时,调用createMemento方法,创建event的Memento对象
     * 返回的Memento对象类型依然是LogEvent,故该类即是Originator也是Memento
     */
    private static LogEvent convertMutableToLog4jEvent(final LogEvent event) {
        return event instanceof Log4jLogEvent ? event : Log4jLogEvent.createMemento(event);
    }
    
    /**
     * 参数event对应备忘录模式中的Memento
     * 该方法将含有备忘状态信息的event进行打印
     */
    public void toSerializable(final LogEvent event, final Writer writer) {
        
        // convertMutableToLog4jEvent(event)返回备忘对象
        // writeValue,对备忘对象进行打印
        objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event)));
        writer.write(eol);
        if (includeNullDelimiter) {
            writer.write('\0');
        }
        markEvent();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值