设计模式之备忘录模式
定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
如游戏中的存档,各种编辑器中的后退、撤销功能。
结构
- 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;
}
}
总结
优点
- 提供一种可以恢复状态的机制,可以很方便的将数据恢复到某个历史的状态。
- 实现了内部状态的封装,除了创建它的发起人,其他对象都不能访问这些状态信息。
缺点
- 如果要保存的内部状态信息过多,将会占用比较大的内存资源。
本质
备忘录模式的本质是保存和恢复内部状态,保存是手段,恢复才是目的。
使用场景
- 需要保存一个对象在某个时刻的全部或部分状态,方便在以后需要的时候,可以将该对象恢复到先前的状态。
参考
大战设计模式【22】—— 备忘录模式
设计模式的征途—20.备忘录(Memento)模式
设计模式(二十)——备忘录模式(游戏角色状态恢复问题)
备忘录模式(详解版)
研磨设计模式-书籍