备忘录模式

2019/10/17 posted in  算法

备忘录模式介绍

备忘录模式是一种行为模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态,这有点像我们平时说的“后悔药”。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保护的这些对象状态的完整性以及内部实现不向外暴露。

备忘录模式的定义

在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可以将该对象恢复到原先保存的状态。

备忘录模式的使用场景

  1. 需要保存一个对象在某个时刻的状态或部分状态
  2. 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。

备忘录模式的简单示例

对备忘录模式来说,比较贴切的场景应该是游戏中的存档功能,该功能就是将游戏进度存储到本地文件系统或者数据库中,下次再次进入时从本地加载进度,使得玩家能够继续上一次的游戏之旅,这里我们就以“使命召唤”这款游戏为例来简单演示一下备忘录模式的实现。
首先我们建立游戏类、备忘录类、Caretaker类,玩游戏到某个节点对游戏进行存档,然后退出游戏,再重新进入时从存档中读取进度,并且进入存档时的进度。

游戏类

/**
 * 使命召唤游戏(数据模型可能不太合理,这里我们只是简单演示)
 */
public class CallOfDuty {
    private int mCheckpoint = 1;
    private int mLifeValue = 100;
    private String mWeapon = "沙漠之鹰";
    
    //玩游戏
    public void play() {
        System.out.println("玩游戏:" + String.format("第%d关", mCheckpoint) + " 奋战杀敌中");
        mLifeValue -= 10;
        System.out.println("进度升级啦");
        mCheckpoint++;
        System.out.println("到达" + String.format("第%d关", mCheckpoint));
    }
    
    //退出游戏
    public void quit() {
        System.out.println("----------");
        System.out.println("退出前的游戏属性:" + this.toString());
        System.out.println("退出游戏");
        System.out.println("----------");
    }
    
    //创建备忘录
    public Memoto createMemoto() {
        Memoto memoto = new Memoto();
        memoto.mCheckpoint = mCheckpoint;
        memoto.mLifeValue = mLifeValue;
        memoto.mWeapon = mWeapon;
        return memoto;
    }
    
    //恢复游戏
    public void restore(Memoto memoto) {
        this.mCheckpoint = memoto.mCheckpoint;
        this.mLifeValue = memoto.mLifeValue;
        this.mWeapon = memoto.mWeapon;
        System.out.println("恢复后的游戏属性:" + this.toString());
    }
    
    @Override
    public String toString() {
        return "CallOfDuty [mCheckpoint = " + mCheckpoint + ", mLifeValue=" + mLifeValue + ", Weapon=" + mWeapon + "]";
    }
}

在CallOfDuty游戏类中,我们存储了几个关键字段,关卡、人物的生命值、武器,当调用play函数玩游戏时,我们对关卡和人物的生命值进行修改。在该类中可以通过createMemoto函数来创建该用户的备忘录对象,也就是将自身的状态保存到一个Memoto对象中。外部可以通过restore函数将CallOfDuty对象的状态从备忘录对象中恢复。

备忘录类

/**
 * 备忘录
 */
public class Memoto {
    public int mCheckpoint;
    public int mLifeValue;
    public String mWeapon;
    
    @Override
    public String toString() {
        return "Memoto [mCheckpoint=" + mCheckpoint + ", mLifeValue=" + mLifeValue + ", mWeapon=" + mWeapon + "]";
    }
} 

这是一个无状态、无操作的实体类,只负责用来存储Originator角色的一些数据,房子外部直接访问Originator。

而备忘录的操作者则是Caretaker角色

Caretaker

/**
 * Caretaker,负责管理Memoto
 */
public class Caretaker {
    Memoto mMemoto; //备忘录
    
    /**
     * 存档
     */
    public void archive(Memoto memoto) {
        this.mMemoto = memoto;
    } 
    
    /**
     * 获取存档
     */
    public Memoto getMemoto() {
        return mMemoto;
    } 
} 

Caretaker类的职责很简单,就是负责管理Memoto对象

客户端

public class Client {
    public static void main(String[] args) {
        //构建游戏对象
        CallOfDuty game = new CallOfDuty();
        //打游戏
        game.play();
        
        Caretaker caretaker = new Caretaker();
        //游戏存档
        caretaker.archive(game.createMemoto());
        //退出游戏
        game.quit();
        //恢复游戏
        CallOfDuty newGame = new CallOfDuty();
        newGame.restore(caretaker.getMemoto());
    }
}

CallOfDuty是需要存储数据的对象,在这里并没有直接存储CallOfDuty的对象,而是通过Memoto对CallOfDuty对象的数据进行存储,然后再存储Memoto对象,最终对Memoto的存取操作则交给Caretaker对象。在这个过程中,各个角色职责清晰、单一,代码也比较简单,即对外屏蔽了对CallOfDuty角色的直接访问,在满足了对象状态存取功能的同时也使得该模块的结构保持清晰、整洁。

总结

备忘录模式是在不破坏封装的条件下,通过备忘录对象存储另外一个对象内部状态的快照,在将来合适的时候把这个对象还原到存储起来的状态。

优点

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  • 实现了信息的封装,使得用户不需要关心状态的保存细节

缺点

消耗资源,如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。