很多的时候,因为从事的职业分工不同,做服务端的程序员几乎用不到Java的备忘录模式,但是在其他的领域,这种模式就用的多了,比如编写PC端的应用的,Android程序员。

那么备忘录模式是什么呢?

备忘录模式的定义:

在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。

从上面的定义可以看出,这种模式是用来保存对象的状态的。保存状态的用处就是为了恢复到以前的状态。

了解了备忘录模式的功能和作用,想必会想起很多的功能用到了备忘录模式,比如:

1.浏览器的后退和前进

2. 文本编辑器的撤回和重做

3. 棋类游戏的悔棋

4. GIt版本管理

我们来分析一下角色:
Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。

Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。

Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计。

接下来我们就以悔棋来作为示例,实现备忘录模式:

首先是棋子颜色的枚举:

public enum Color {

    WHITE(1,"白"),
    BLACK(2,"黑");

    private Integer color;

    private String colorDesc;

    Color(Integer color, String colorDesc){
        this.color = color;
        this.colorDesc = colorDesc;
    }

    public Integer getColor() {
        return color;
    }

    public void setColor(Integer color) {
        this.color = color;
    }

    public String getColorDesc() {
        return colorDesc;
    }

    public void setColorDesc(String colorDesc) {
        this.colorDesc = colorDesc;
    }
}

然后是棋子类:

/**
 * 棋子类,这是原发器,被保存的对象
 */
public class ChessMan {

    private Color color; // 棋子颜色

    private int x; // 棋子横坐标

    private int y; // 棋子纵坐标

    /**
     * 保存到备忘录
     * @return 棋子的备忘录
     */
    public ChessMemento saveToMemento(){
        return new ChessMemento(this.color,this.x,this.y);
    }

    /**
     * 从备忘录恢复棋子的状态
     * @param chessMemento 备忘录
     */
    public void restoreToMemento(ChessMemento chessMemento){
        this.color = chessMemento.getColor();
        this.x = chessMemento.getX();
        this.y = chessMemento.getY();

    }

    public void show(){
        System.out.println("当前棋子是 " + this.color.getColorDesc() + "棋子 ,位置: 横坐标 :" + this.x + " 纵坐标 :" + this.y);
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

然后是备忘录:

/**
 * 备忘录,备忘录需要覆盖原发器的属性
 */
public class ChessMemento {

    private Color color; // 棋子颜色

    private int x; // 棋子横坐标

    private int y; // 棋子纵坐标

    ChessMemento(Color color,int x,int y){
        this.color = color;
        this.x = x;
        this.y = y;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

然后是负责人,管理备忘录的类:

/**
 * 负责人,管理备忘录的
 */
public class MementoCaretaker {

    //维护一个备忘录的集合
    private List<ChessMemento> mementoList = new ArrayList<>();

    private int index = -1;


    /**
     * 继续 前进
     */
    public void goOn(ChessMan chessMan){
        index++;
        this.mementoList.add(index,chessMan.saveToMemento());
        // 如果是悔棋多步下棋,那么就把前面的记录清空
        if(index < this.mementoList.size() - 1 ){
            for (int i = index + 1 ; i < this.mementoList.size() ; i++){
                this.mementoList.remove(i);
            }
        }
    }

    /**
     * 悔棋
     * @return 上一步棋子
     */
    public ChessMan undo(){
        ChessMan chessMan = new ChessMan();
        if (index >= 0){
            chessMan.restoreToMemento(this.mementoList.get(--index));
        } else {
            chessMan = null;
        }
        return chessMan;
    }

    /**
     * 恢复悔棋
     */
    public ChessMan redo(){
        ChessMan chessMan = new ChessMan();
        index++;
        if (index < this.mementoList.size()){
            chessMan.restoreToMemento(this.mementoList.get(index));
        } else {
            chessMan = null;
        }
        return chessMan;
    }

}

客户端和测试代码:

public class Client {


    private static MementoCaretaker mementoCaretaker = new MementoCaretaker();

    public static void main(String[] args) {
        ChessMan chessMan = new ChessMan();
        chessMan.setColor(Color.WHITE);
        chessMan.setX(1);
        chessMan.setY(1);
        play(chessMan);
        chessMan.setY(2);
        play(chessMan);
        chessMan.setX(2);
        play(chessMan);
        chessMan.setColor(Color.BLACK);
        chessMan.setX(3);
        play(chessMan);
        chessMan.setY(3);
        play(chessMan);

        undo();
        undo();
        redo();
        redo();


        undo();
        undo();
        chessMan.setX(4);
        play(chessMan);
        redo();
        redo();


    }

    /**
     * 下棋
     * @param chessMan 棋子
     */
    public static void play(ChessMan chessMan){
        mementoCaretaker.goOn(chessMan);
        chessMan.show();
    }

    /**
     * 悔棋
     */
    public static void undo(){
        ChessMan chess = mementoCaretaker.undo();
        if (chess != null){
            System.out.print("悔棋成功,");
            chess.show();
        } else {
            System.out.println("悔棋失败,当前没有下棋,不可以悔棋,请下棋");
        }

    }

    /**
     * 恢复悔棋
     */
    public static void redo(){
        ChessMan chessMan = mementoCaretaker.redo();
        if (chessMan != null){
            System.out.print("恢复成功,");
            chessMan.show();
        } else {
            System.out.println("恢复失败,请继续下棋,没有可以恢复的状态");
        }

    }
}

测试结果:

当前棋子是 白棋子 ,位置: 横坐标 :1 纵坐标 :1
当前棋子是 白棋子 ,位置: 横坐标 :1 纵坐标 :2
当前棋子是 白棋子 ,位置: 横坐标 :2 纵坐标 :2
当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :2
当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :3
悔棋成功,当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :2
悔棋成功,当前棋子是 白棋子 ,位置: 横坐标 :2 纵坐标 :2
恢复成功,当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :2
恢复成功,当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :3
悔棋成功,当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :2
悔棋成功,当前棋子是 白棋子 ,位置: 横坐标 :2 纵坐标 :2
当前棋子是 黑棋子 ,位置: 横坐标 :4 纵坐标 :3
恢复成功,当前棋子是 黑棋子 ,位置: 横坐标 :3 纵坐标 :3
恢复失败,请继续下棋,没有可以恢复的状态

以上就是通过下棋来实现备忘录模式。

备忘录总结:

备忘录模式优点:

它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。

备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

备忘录模式的主要缺点:

资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。