Java设计模式之-备忘录模式(Memento)

看到“备忘录”这个名字的时候,我基本上不知道这个模式需要做的事情。而后又翻看了一下GoF的书,它的Intent是这个样子的:

Memento: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.

在不破坏封装的前提下,获得对象的内部状态并外部化,用于以后的状态恢复行为。看这个解释,可以发现备忘录模式的要点有两方面:

  • 外部化(externalize);
  • 恢复(restore);

为了要恢复,所以得进行外部化。那么如何进行外部化也就直接关系到如何恢复。我们来举两个常见的场景,看看一般我们可以如何进行外部化和恢复。

撤销和重做

撤销(Undo)和重做(Redo)是我们在日常学习工作中经常用到的操作。为了要实现撤销和重做,我们要将每一个动作的状态保存起来(外部化),在适当的时候拿出来进行恢复。
我们首先定义两个接口,定义撤销和重做的相关方法。

public interface Undoable {
    public void undo();
    public boolean canUndo();
}

public interface Redoable {
    public void redo();
    public boolean canRedo();
}

然后我们写一个类来实现这两个接口。假设我们要写一个TextField,这个控件可以进行移动,默认的锚点是左上角的点,然后还可以往这个控件中增加字符。

package com.designpattern.memento;

import java.awt.*;
import java.util.ArrayDeque;
import java.util.Deque;

public class TextField implements Undoable, Redoable, Cloneable {

    private Point currentPoint = new Point(0, 0);
    private StringBuilder text = new StringBuilder();
    private Deque historyStack = new ArrayDeque<>();
    private Deque futureStack = new ArrayDeque<>();

    //移动文本框
    public void move(int x, int y) {
        restoreState();
        currentPoint.move(x, y);
    }

    //在文本框中输入文字
    public void append(String str) {
        restoreState();
        text.append(str);
    }

    //保存状态
    private void restoreState() {
        historyStack.push((TextField) clone());
    }

    @Override
    public void redo() {
        if (canRedo() == false) {
            System.out.println("No redo state found......");
            return;
        }
        TextField instance = futureStack.pop();
        this.currentPoint = new Point(instance.currentPoint);
        this.text = new StringBuilder(instance.text.toString());
        this.historyStack.push(instance);
    }

    @Override
    public boolean canRedo() {
        return futureStack.size() != 0;
    }

    @Override
    public void undo() {
        if (canUndo() == false) {
            System.out.println("No undo state found......");
            return;
        }
        this.futureStack.push((TextField) clone());
        TextField instance = historyStack.pop();
        this.currentPoint = new Point(instance.currentPoint);
        this.text = new StringBuilder(instance.text.toString());
    }

    @Override
    public boolean canUndo() {
        return historyStack.size() != 0;
    }

    @Override
    public Object clone() {
        try {
            super.clone();
            TextField instance = new TextField();
            instance.currentPoint = new Point(currentPoint);
            instance.text = new StringBuilder(text.toString());
            instance.historyStack = new ArrayDeque<>(historyStack);
            instance.futureStack = new ArrayDeque<>(futureStack);
            return instance;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    //打印输出
    public void reportMyself() {
        System.out.printf("Pos=(%d, %d) Text=\"%s\"%n", currentPoint.x, currentPoint.y, text.toString());
    }
}

我们通过两个Queue来对状态进行储存,当调用undo或redo的时候,从相应的Queue中获得状态,然后覆盖给当前的对象。这样一来,我们就实现了备忘录模式。
注意其中用到了上篇文章中说到的原型模式(Prototype),由于我们必须进行深拷贝,所以实现了Cloneable接口,新建了一块内存保存对象的状态。
其实我们也可以不保存TextField对象,而是针对其中的关键数据,如Point的x/y坐标,StringBuilder对应的String进行保存,这样能够更节省内存。


SVN

其实我们日常使用的SVN,也可以算作是一种备忘录模式。它将不同版本的文件统一保存起来,当我们需要获得某一个时刻的文件版本时,可以从SVN库中拉取对应记录。
其实这有一点数据仓库的感觉,将每一版本的截面数据保存起来,用于后续的恢复和查看。而我们进行的文件保存与恢复,或者序列化与反序列化,如果按照备忘录模式的定义,也可以算作一种体现形式。

你可能感兴趣的:(Java设计模式之-备忘录模式(Memento))