设计模式-备忘录模式

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

现实场景是比较多的,比如文档编辑中的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中获取对应时间节点的数据。

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容