设计模式-备忘录模式

备忘录模式:备忘录模式提供了一种状态恢复机制,使得用户可以很方便的回到一个特定的历史步骤。

现实场景是比较多的,比如文档编辑中的ctrl+z,或者代码提交中的回滚,都是这种概念。

简单的来说,就是对于一个对象,我先在某个地方保存它当前的一个状态,之后当它经历过一些操作之后想恢复到当初的那个状态,那再把之前保存的那个状态拿出来就可以恢复了。

这里面涉及到三种角色:发起人角色(也就是一个主体对象,他的状态会发生变化,之后又可能要改变),备忘录角色(负责记录发起人的状态),管理者角色(对备忘录进行管理,只能保存和获取,不能修改)
这里有两种方式:白箱备忘录,黑箱备忘录

举个实际的例子,手机的初始化也是一种状态恢复,假设初始的时候手机有一系类的参数设置,经过一段时间的使用之后,这些参数都发生了变化,此时用户想把手机的其他数据都清理掉,让它回到最初的状态,我们用备忘录模式来实现一下。
首先看简单的白箱备忘录:

发起人:Phone

public class Phone {
    private Integer memory;
    private Integer disk;
    private String system;
    //初始状态
    public Phone() {
        this.memory = 128;
        this.disk = 500;
        this.system = "andriod";
    }
    // 保存初始状态,这里需要一个备忘录对象
    public StateMemento saveState(){
        // 在备忘录中记录这个phone的初始状态
        return new StateMemento(memory, disk, system);
    }
    // 恢复状态,从备忘录中获取之前保存的值
    public void recover(StateMemento stateMemento){
        this.memory = stateMemento.getMemory();
        this.disk = stateMemento.getDisk();
        this.system = stateMemento.getSystem();
    }

    public Integer getMemory() {
        return memory;
    }
    public void setMemory(Integer memory) {
        this.memory = memory;
    }
    public Integer getDisk() {
        return disk;
    }
    public void setDisk(Integer disk) {
        this.disk = disk;
    }
    public String getSystem() {
        return system;
    }
    public void setSystem(String system) {
        this.system = system;
    }
    @Override
    public String toString() {
        return "Phone{" +
                "memory=" + memory +
                ", disk=" + disk +
                ", system='" + system + '\'' +
                '}';
    }
}

备忘录:

public class StateMemento {
    private Integer memory;
    private Integer disk;
    private String system;

    public StateMemento(Integer memory, Integer disk, String system) {
        this.memory = memory;
        this.disk = disk;
        this.system = system;
    }

    public Integer getMemory() {
        return memory;
    }

    public void setMemory(Integer memory) {
        this.memory = memory;
    }

    public Integer getDisk() {
        return disk;
    }

    public void setDisk(Integer disk) {
        this.disk = disk;
    }

    public String getSystem() {
        return system;
    }

    public void setSystem(String system) {
        this.system = system;
    }
}

管理者:

public class MementoManager {
    // 管理中保存者备忘录对象,发起人保存的时候把备忘录保存到这里,等要恢复状态的时候再从这里获取之前的备忘录
    private StateMemento stateMemento;

    public StateMemento getStateMemento() {
        return stateMemento;
    }

    public void setStateMemento(StateMemento stateMemento) {
        this.stateMemento = stateMemento;
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        Phone huawei = new Phone();
        System.out.println("-------初始状态------");
        System.out.println(huawei);
        // 保存初始状态
        MementoManager manager = new MementoManager();
        manager.setStateMemento(huawei.saveState());
        System.out.println("------开始使用手机-------");
        System.out.println("---消耗内存:100G");
        huawei.setMemory(huawei.getMemory() - 100);
        System.out.println("----消耗磁盘200G");
        huawei.setDisk(huawei.getDisk() - 200);
        System.out.println("重装个系统吧,鸿蒙不错");
        huawei.setSystem("鸿蒙系统");
        System.out.println("-----当前状态----------");
        System.out.println(huawei.toString());
        System.out.println("-------恢复状态--------");
        huawei.recover(manager.getStateMemento());
        System.out.println(huawei.toString());

    }
}

白箱备忘录有一个安全性问题,那就是管理者是拥有备忘录的,那么这样那就可能会对备忘录的内容进行修改,这样就没办法保证保存的备忘录的数据准确性。

黑箱备忘录:这是对白箱备忘录的改进,我们只定义个备忘录角色的接口,便于管理者保存,但是具体的备忘录对象定义在发起人内部,这样管理者无法获取备忘录对象对其内部属性进行修改。

发起人:

public class Phone {
    private Integer memory;
    private Integer disk;
    private String system;
    //初始状态
    public Phone() {
        this.memory = 128;
        this.disk = 500;
        this.system = "andriod";
    }
    // 保存初始状态,这里需要一个备忘录对象
    public Memento saveState(){
        // 在备忘录中记录这个phone的初始状态
        return new StateMemento(memory, disk, system);
    }

    // 恢复状态,从备忘录中获取之前保存的值
    public void recover(Memento memento){
        StateMemento stateMemento = (StateMemento) memento;
        this.memory = stateMemento.getMemory();
        this.disk = stateMemento.getDisk();
        this.system = stateMemento.getSystem();
    }

    // 备忘录使用内部类的方式,这样可以防止外部获取对象修改
    private class StateMemento implements Memento{
        private Integer memory;
        private Integer disk;
        private String system;

        public StateMemento(Integer memory, Integer disk, String system) {
            this.memory = memory;
            this.disk = disk;
            this.system = system;
        }
        public Integer getMemory() {
            return memory;
        }
        public void setMemory(Integer memory) {
            this.memory = memory;
        }
        public Integer getDisk() {
            return disk;
        }
        public void setDisk(Integer disk) {
            this.disk = disk;
        }
        public String getSystem() {
            return system;
        }
        public void setSystem(String system) {
            this.system = system;
        }
    }

    public Integer getMemory() {
        return memory;
    }
    public void setMemory(Integer memory) {
        this.memory = memory;
    }
    public Integer getDisk() {
        return disk;
    }
    public void setDisk(Integer disk) {
        this.disk = disk;
    }
    public String getSystem() {
        return system;
    }
    public void setSystem(String system) {
        this.system = system;
    }
    @Override
    public String toString() {
        return "Phone{" +
                "memory=" + memory +
                ", disk=" + disk +
                ", system='" + system + '\'' +
                '}';
    }
}

备忘录:

// 定义这个接口只是为了让管理者可以管理备忘录,实现类放在发起人内部,这样就可以防止管理者篡改其内部数据
public interface Memento {
}

管理者:

public class MementoManager {
    // 管理中保存者备忘录对象,发起人保存的时候把备忘录保存到这里,等要恢复状态的时候再从这里获取之前的备忘录
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        Phone huawei = new Phone();
        System.out.println("-------初始状态------");
        System.out.println(huawei);
        // 保存初始状态
        MementoManager manager = new MementoManager();
        manager.setMemento(huawei.saveState());
        System.out.println("------开始使用手机-------");
        System.out.println("---消耗内存:88G");
        huawei.setMemory(huawei.getMemory() - 88);
        System.out.println("----消耗磁盘200G");
        huawei.setDisk(huawei.getDisk() - 55);
        System.out.println("重装个系统吧,鸿蒙");
        huawei.setSystem("鸿蒙");
        System.out.println("-----当前状态----------");
        System.out.println(huawei.toString());
        System.out.println("-------恢复状态--------");
        huawei.recover(manager.getMemento());
        System.out.println(huawei.toString());
    }
}

备忘录模式从概念上比较容易理解,实现上也不复杂。感觉似乎也挺有用,而且这里有一个扩展点,这里的例子只是保存了一个节点的状态,实际上我们可能需要保存很多个节点的状态,比如我们代码提交记录,就需要保存多个提交节点的记录。这样我们可以改造一下,比如这个备忘录对象我们可以用Map的格式,给每一个状态一个key,比如当前时间或者生成一个唯一标识,value就是要保存的数据,这样当我们想要恢复的时候,就可以从map中获取对应时间节点的数据。

但是备忘录模式有一个问题,我们可以看到备忘录的对象都是在内存中保存的,如果要保存的数据比较多,那么对内存的消耗就会比较大,所以使用前需要评估数据的量。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容