备忘录设计模式

  • Post author:
  • Post category:其他




备忘录设计模式




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

中使用备忘录模式,显得更为简洁,却也能达到备忘的作用。

Log4j



Log4jLogEvent

类,充当

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();
    }
}



版权声明:本文为Keiissland原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。