设计模式之备忘录(Memento)

发布时间 2023-05-27 15:41:56作者: 香蕉白羽毛

概述

备忘录模式(Memento Pattern),是行为型模式设计模式之一,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护被保存的这些对象状态的完整性以及内部实现不向外暴露,本篇博客,我们就来一起学习备忘录模式。

使用场景

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

UML

 
备忘录模式UML

Originator: 负责创建一个备忘录,可以记录、恢复自身的内部状态,同时 Originator 还可以根据需要决定 Memento 存储自身的哪些内部状态。

Memento:备忘录角色,用于存储 Originator 的内部状态,并且可以防止 Originator 以外的对象访问 Memento。

CareTaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。

白箱备忘录模式

Java中,实现“宽”和“窄”两个接口并不容易,如果暂时忽略两个接口的区别,仅为备忘录角色提供一个宽接口的话,备忘录的内部存储状态就对所有对象公开,这就是“白箱实现”。
“白箱”实现破坏了封装性,但是通过程序员自律,可以方便地实现备忘录模式。

package com.dsguo.memento;

public class Memento {

    private String state;

    public Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
}
package com.dsguo.memento;

public class Originator {

    private String state;

    /**
     * 工厂方法,返回一个新的备忘录对象
     * @return
     */
    public Memento createMemento() {
        return new Memento(state);
    }


    /**
     * 将发起人恢复到备忘录对象所记载的状态
     * @param memento
     */
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
    }

    public String getState() {
        System.out.println("当前状态:" + state);
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
package com.dsguo.memento;

public class Caretaker {

    private Memento memento;

    public Memento retrieveMemento() {
        return this.memento;
    }

    public void saveMemento(Memento memento) {
        this.memento = memento;
    }
}
package com.dsguo.memento;

public class Client {

    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        //改变发起人的状态
        originator.setState("on");
        originator.getState();

        //创建备忘录对象,并将发起人对象的状态存储起来
        caretaker.saveMemento(originator.createMemento());

        originator.setState("off");
        originator.getState();


        //恢复发起人对象的状态
        originator.restoreMemento(caretaker.retrieveMemento());
        originator.getState();
    }
}

运行结果

当前状态:on
当前状态:off
当前状态:on

黑箱备忘录模式

黑箱备忘录模式相比白箱备忘录模式有如下区别:
  • 将Memento设成Originator类的内部类
  • 将Memento的方法全部设成私有方法,这样只有它自己和发起人Originator可以调用;
  • 在外部提供一个标识接口MementoIF给Caretaker以及其他对象,标识接口MementoIF没有提供任何方法,因此对外部来说Memento对象的内容都是不可见的。
package com.dsguo.memento2;

/**
 * 标识接口(窄接口)
 */
public interface MementoIF {

}
package com.dsguo.memento2;

import java.util.Vector;

public class Originator {

    private Vector<Object> states;
    private int index;

    public Originator() {
        states = new Vector<Object>();
        index = 0;
    }

    /**
     * 工厂方法,返回一个新的备忘录对象
     * @return
     */
    public MementoIF createMemento() {
        return new Memento(this.states, index);
    }

    /**
     * 将发起人恢复到备忘录对象所记载的状态
     * @param memento
     */
    public void restoreMemento(MementoIF memento) {
        states = ((Memento)memento).getStates();
        index = ((Memento)memento).getIndex();
    }

    /**
     * 状态的复制方法
     * @param state
     */
    public void setState(Object state) {
        this.states.addElement(state);
        index++;
    }

    /**
     * 辅助方法,打印出所有状态
     */
    public void printStates() {
        System.out.println("total number of states:" + index);
        for (Object o : states) {
            System.out.println(o.toString());
        }
    }

    protected class Memento implements MementoIF {

        private Vector<Object> saveStates;
        private int saveIndex;

        /**
         * states一定是Vector<Object类型的变量,复制后也一定是Vector<Object的变量
         * @param states
         * @param index
         */
        private Memento(Vector<Object> states, int index) {
            //保存客户端传来的状态对象的拷贝,否则客户端的修改会影响到保存的状态。
            saveStates = (Vector<Object>)states.clone();
            saveIndex = index;
        }

        private Vector<Object> getStates() {
            return saveStates;
        }

        private int getIndex() {
            return saveIndex;
        }
    }
}

package com.dsguo.memento2;

import java.util.Vector;

public class Caretaker {
    private Originator o;
    private Vector<MementoIF> mementos = new Vector<MementoIF>();
    private int currentIndex;

    public Caretaker(Originator o) {
        this.o = o;
        currentIndex = 0;
    }

    /**
     * 创建一个新的检查点
     */
    public void createMemento() {
        mementos.add(o.createMemento());
        currentIndex++;
    }

    /**
     * 将发起人恢复到某个检查点
     * @param index
     */
    public void restoreMemento(int index) {
        o.restoreMemento(mementos.elementAt(index));
    }

    /**
     * 删除某个检查点
     * @param index
     */
    public void removeMemento(int index) {
        mementos.removeElementAt(index);
    }

}
package com.dsguo.memento2;

public class Client {

   public static void main(String[] args) {
       Originator originator = new Originator();
       Caretaker caretaker = new Caretaker(originator);

       originator.setState("state0");
       caretaker.createMemento();
       originator.setState("state1");
       caretaker.createMemento();
       originator.setState("state2");
       caretaker.createMemento();

       //打印出所有状态
       originator.printStates();
       System.out.println("restoring to 3");
       caretaker.restoreMemento(1);
       originator.printStates();

   }

}

运行结果:

total number of states:3
state0
state1
state2
restoring to 3
total number of states:2
state0
state1

跟例子1相比,负责人角色除了负责保存状态之外,还负责发起人状态的恢复,功能增强了。

总结

黑箱”备忘录的实现中,将Memento类做成Originator的内部类,并将其方法全部设置成private,其实这样一般来说就已经足够了,不需要再使用窄接口MementoIF。因为这样做的话外部拿到Memento类的实例,由于其方法都是private的,所以该方法只有Originator类可以调用,其它类是调用不了的,也就无法修改其中的内容。



作者:ikonan
链接:https://www.jianshu.com/p/0d84adfda53e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。