设计模式之备忘录模式

定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
如游戏中的存档,各种编辑器中的后退、撤销功能。

结构

  • Originator,发起人角色,一个普通的业务处理类,可以根据自身创建备忘录对象,根据备忘录数据恢复自身。
  • Memento,备忘录角色,负责存储发起人的内部数据,在需要时根据备忘录来恢复发起人。
  • Caretaker,备忘录管理者,提供保存和获取备忘录的功能。

简单实现

发起人

/**
 * 发起者
 */
public class Originator {

  private String state;

  public void setState(String state) {
    this.state = state;
  }

  public String getState() {
    return state;
  }

  public Memento createMemento() {
    return new Memento(state);
  }

  public void restoreMemento(Memento m) {
    this.setState(m.getState());
  }

}

备忘录实现

/**
 * 备忘录
 */
public class Memento {

  private String state;

  public Memento(String state) {
    this.state = state;
  }

  public void setState(String state) {
    this.state = state;
  }

  public String getState() {
    return state;
  }
}

备忘录管理者

/**
 * 管理者
 */
public class Caretaker {

  private Memento memento;

  public void setMemento(Memento m) {
    memento = m;
  }

  public Memento getMemento() {
    return memento;
  }
}

客户端

public class Client {

  public static void main(String[] args) {
    Originator or = new Originator();
    Caretaker cr = new Caretaker();
    or.setState("S0");
    System.out.println("初始状态:" + or.getState());
    cr.setMemento(or.createMemento()); //保存状态
    or.setState("S1");
    System.out.println("新的状态:" + or.getState());
    or.restoreMemento(cr.getMemento()); //恢复状态
    System.out.println("恢复状态:" + or.getState());
  }

}

输出结果为

初始状态:S0
新的状态:S1
恢复状态:S0

使用窄接口实现不可修改的备忘录

备忘录对象不应该被除了发起人对象之外的对象访问和修改,这里我们引入一个窄接口,不包含任何方法,只是作为标识接口。具体的备忘录实现只有发起人才能访问。

/**
 * 备忘录
 */
public interface Memento {

}

发起人,内部包含备忘录的具体实现

/**
 * 发起者
 */
public class Originator {

  private String state;

  public void setState(String state) {
    this.state = state;
  }

  public String getState() {
    return state;
  }

  public Memento createMemento() {
    return new MementoImpl(state);
  }

  public void restoreMemento(Memento m) {
    this.setState(((MementoImpl)m).getState());
  }

  private static class MementoImpl implements Memento {

    private String state;

    public MementoImpl(String state) {
      this.state = state;
    }

    public void setState(String state) {
      this.state = state;
    }

    public String getState() {
      return state;
    }
  }
}

备忘录管理者和客户端都不变。

实现撤销和恢复功能

之前学习到 命令模式 实现撤销功能时,有两种思路来实现

  • 一种是补偿式,又称反操作式,比如被撤销的操作是添加,撤销就是删除。
  • 另一种是存储恢复式,将操作前的状态记录下来,撤销的时候直接恢复回去就可以了。

这里就使用第二种方式再实现一次。

命令接收者

/**
 * 命令接收者
 */
public class Receiver {

  /**
   * 文本内容
   */
  private String textContent = "";

  /**
   * 文本追加
   */
  public void append(String target) {
    System.out.println("操作前内容:" + textContent);
    textContent = textContent.concat(target);
    System.out.println("操作后内容:" + textContent);
  }

  /**
   * 文本删除
   */
  public void remove(String target) {
    System.out.println("操作前内容:" + textContent);
    if (textContent.endsWith(target)) {
      textContent = textContent.substring(0, textContent.length() - target.length());
    }
    System.out.println("操作后内容:" + textContent);
  }

  public Memento createMemento() {
    return new MementoImpl(textContent);
  }

  public void restoreMemento(Memento m) {
    System.out.println("操作前内容:" + textContent);
    textContent = ((MementoImpl) m).getTextContent();
    System.out.println("操作后内容:" + textContent);
  }

  public static class MementoImpl implements Memento {

    private String textContent;

    public MementoImpl(String textContent) {
      this.textContent = textContent;
    }

    public String getTextContent() {
      return textContent;
    }
  }
}

命令接收者就是备忘录模式中的发起人角色。

备忘录接口

/**
 * 备忘录
 */
public interface Memento {

}

命令接口

public interface Command {

  /**
   * 命令执行
   */
  void execute();

  /**
   * 命令撤销
   */
  void undo(Memento m);

  /**
   * 创建备忘录
   */
  Memento createMemento();
}

根据备忘录对象来撤销。

抽象命令类

public abstract class AbstractCommand implements Command {


  protected Receiver receiver;
  protected String target;

  protected AbstractCommand(Receiver receiver, String target) {
    this.receiver = receiver;
    this.target = target;
  }

  @Override
  public void undo(Memento m) {
    receiver.restoreMemento(m);
  }

  @Override
  public Memento createMemento() {
    return receiver.createMemento();
  }
}

对于所有的命令实现,它们的撤销都是一样的,都是根据备忘录对象来撤销,所以这里实现一个公共的命令对象。

具体的命令实现

/**
 * 文本追加命令
 */
public class AppendCommand extends AbstractCommand {

  public AppendCommand(Receiver receiver, String target) {
    super(receiver, target);
  }

  @Override
  public void execute() {
    receiver.append(target);
  }

}
/**
 * 文本删除命令
 */
public class RemoveCommand extends AbstractCommand {

  public RemoveCommand(Receiver receiver, String target) {
    super(receiver, target);
  }

  @Override
  public void execute() {
    receiver.remove(target);
  }

}

文本编辑器

import java.util.Stack;

/**
 * 文本编辑器,支持撤销和恢复
 */
public class TextEditor {

  private Command command;
  //操作的历史记录
  private Stack<Command> undoCommandStack = new Stack<>();
  //记录操作前和操作后
  private Stack<Memento[]> undoMementoStack = new Stack<>();
  //撤销的历史记录
  private Stack<Command> redoCommandStack = new Stack<>();
  //记录撤销前和撤销后
  private Stack<Memento[]> redoMenentoStack = new Stack<>();

  public void setCommand(Command command) {
    this.command = command;
  }

  public void editText() {
    Memento m1 = command.createMemento();
    command.execute();
    Memento m2 = command.createMemento();
    undoCommandStack.push(command);
    undoMementoStack.push(new Memento[]{m1, m2});
  }

  /**
   * 撤销功能
   */
  public void undoText() {
    if (!undoCommandStack.isEmpty()) {
      Command command = undoCommandStack.pop();
      Memento[] mementos = undoMementoStack.pop();
      command.undo(mementos[0]);
      redoCommandStack.push(command);
      redoMenentoStack.push(mementos);
    }
  }

  /**
   * 恢复功能
   */
  public void redoText() {
    if (!redoCommandStack.isEmpty()) {
      Command command = redoCommandStack.pop();
      Memento[] mementos = redoMenentoStack.pop();
      command.undo(mementos[1]);
      undoCommandStack.push(command);
      undoMementoStack.push(mementos);
    }
  }
}

内部保存命令执行的历史记录、命令执行前及执行后的备忘录的历史记录(可撤销的列表)和撤销执行的历史记录、
撤销前及撤销后的备忘录的历史记录(可恢复的列表),有撤销才会有恢复,文本编辑器相当于备忘录模式中的备忘录管理者角色。

客户端使用

public class Client {

  public static void main(String[] args) {
    //组装命令和执行者
    Receiver receiver = new Receiver();
    TextEditor textEditor = new TextEditor();
    //追加hello
    textEditor.setCommand(new AppendCommand(receiver, "hello"));
    textEditor.editText();
    //追加world
    textEditor.setCommand(new AppendCommand(receiver, "world"));
    textEditor.editText();
    //删除orld
    textEditor.setCommand(new RemoveCommand(receiver, "orld"));
    textEditor.editText();
    //撤销
    textEditor.undoText();
    //撤销
    textEditor.undoText();
    //恢复
    textEditor.redoText();
  }

}

备忘录模式在Spring Webflow中的实现

Spring Webflow 构建于SpringMVC之上,允许实现Web应用程序的"流程",适用于加班单申请,办理登机手续等有状态的流程。

引入maven依赖

<dependency>
  <groupId>org.springframework.webflow</groupId>
  <artifactId>spring-webflow</artifactId>
  <version>2.5.1.RELEASE</version>
</dependency>

示例

Spring Webflow中的StateManageableMessageContext

/**
 * A message context whose internal state can be managed by an external care-taker. State management employs the GOF
 * Memento pattern. This context can produce a serializable memento representing its internal state at any time. A
 * care-taker can then use that memento at a later time to restore any context instance to a previous state.
 * 
 * @author Keith Donald
 */
public interface StateManageableMessageContext extends MessageContext {

	/**
	 * Create a serializable memento, or token representing a snapshot of the internal state of this message context.
	 * @return the messages memento
	 */
	Serializable createMessagesMemento();

	/**
	 * Set the state of this context from the memento provided. After this call, the messages in this context will match
	 * what is encapsulated inside the memento. Any previous state will be overridden.
	 * @param messagesMemento the messages memento
	 */
	void restoreMessages(Serializable messagesMemento);

	/**
	 * Configure the message source used to resolve messages added to this context. May be set at any time to change how
	 * coded messages are resolved.
	 * @param messageSource the message source
	 * @see MessageContext#addMessage(MessageResolver)
	 */
	void setMessageSource(MessageSource messageSource);
}

实现类如下

/**
 * The default message context implementation. Uses a {@link MessageSource} to resolve messages that are added by
 * callers.
 *
 * @author Keith Donald
 */
public class DefaultMessageContext implements StateManageableMessageContext {
	// implementing state manageable message context

	public Serializable createMessagesMemento() {
		return new LinkedHashMap<Object, List<Message>>(sourceMessages);
	}

	@SuppressWarnings("unchecked")
	public void restoreMessages(Serializable messagesMemento) {
		sourceMessages.putAll((Map<Object, List<Message>>) messagesMemento);
	}

	public void setMessageSource(MessageSource messageSource) {
		if (messageSource == null) {
			messageSource = new DefaultTextFallbackMessageSource();
		}
		this.messageSource = messageSource;
	}
}

总结

优点

  1. 提供一种可以恢复状态的机制,可以很方便的将数据恢复到某个历史的状态。
  2. 实现了内部状态的封装,除了创建它的发起人,其他对象都不能访问这些状态信息。

缺点

  1. 如果要保存的内部状态信息过多,将会占用比较大的内存资源。

本质

备忘录模式的本质是保存和恢复内部状态,保存是手段,恢复才是目的。

使用场景

  1. 需要保存一个对象在某个时刻的全部或部分状态,方便在以后需要的时候,可以将该对象恢复到先前的状态。

参考

大战设计模式【22】—— 备忘录模式
设计模式的征途—20.备忘录(Memento)模式
设计模式(二十)——备忘录模式(游戏角色状态恢复问题)
备忘录模式(详解版)
研磨设计模式-书籍

posted @ 2021-09-07 20:44  strongmore  阅读(94)  评论(0编辑  收藏  举报