本文为博主原创文章,请遵守文章最后的版权申明。
备忘录模式(Memento),行为型模式。撤销是一种非常常见的功能,我们几乎天天都要用。相信各位同学对Ctrl+Z的熟悉程度已经不亚于Ctrl+C和Ctrl+V了吧!备忘录模式通过将对象的状态封装到一个包装对象中并存储到外部的手段实现对象状态可以回退(或者说可以实现撤销操作)。
在《GoF设计模式 - 概述》一文中已经讲述了设计原则等基础知识,如果还没有看,请先看完这篇文章。
备忘录模式
角色以及职责
- Originator: 发起者。即我们需要记录状态的目标对象。
- Mememto: 备忘录。用于包装Originator的某一个时刻的状态的对象,可以实现为一个简单的Java Bean。
- Caretaker: 备忘录的看管者。Caretaker 负责存储一系列的Mememto。
适用场景
备忘录模式的针对性还是非常强的,所以适用场景比较明确,就是需要保存对象的历史状态用以支持撤销功能的场景。
实例
本文的例子是一个简单的文本编辑器,我们来用备忘录模式来给这个文本编辑器加上撤销功能。
类图
编码实现
首先来实现Mememto,也就是我们的备忘录类。在本例中,为了使类名更具业务含义,再此将其命名为Version
。备忘录类的实现很简单,在本例中只是一个POJO。
/**
* 版本
* @author: Elvin Zeng
* @date: 17-7-31.
*/
public class Version {
private String content;
private String author;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Version{" +
"content='" + content + '\'' +
", author='" + author + '\'' +
'}';
}
}
接下来实现一个用于模拟文本编辑器的类。真实程序中这个类可能会很复杂,这里为了不干扰备忘录模式的演示,特此将其他所有的操纵界面的代码省略。
/**
* 文本编辑器
* @author: Elvin Zeng
* @date: 17-7-31.
*/
public class Editor {
private String author;
private String content;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
/**
* 创建编辑器当前状态的快照(生成一个版本)
* @return 当前版本
*/
public Version snapshoot(){
Version version = new Version();
version.setAuthor(this.author);
version.setContent(this.content);
return version;
}
/**
* 恢复历史版本
* @param version 要恢复的历史版本
*/
public void revertSnapshot(Version version){
this.content = version.getContent();
this.author = version.getAuthor();
}
@Override
public String toString() {
return "Editor{" +
"author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
然后实现负责存储所有的Version
对象的管理对象编辑器历史版本管理器(VersionManager
),也就是Caretaker。
/**
* 编辑器历史版本管理器
* @author: Elvin Zeng
* @date: 17-7-31.
*/
public class VersionManager {
private List<Version> versions = new LinkedList<>();
public boolean contains(Object o) {
return versions.contains(o);
}
public Iterator<Version> iterator() {
return versions.iterator();
}
public boolean add(Version version) {
return versions.add(version);
}
public boolean remove(Object o) {
return versions.remove(o);
}
public void clear() {
versions.clear();
}
public Version get(int index) {
return versions.get(index);
}
public Version set(int index, Version element) {
return versions.set(index, element);
}
public Version remove(int index) {
return versions.remove(index);
}
public int indexOf(Object o) {
return versions.indexOf(o);
}
public Version pop(){
return this.versions.remove(versions.size() - 1);
}
}
最后是客户端
/**
* Client
* @author: Elvin Zeng
* @date: 17-7-28.
*/
public class App {
public static void main(String[] args) {
Editor editor = new Editor();
VersionManager versionManager = new VersionManager();
editor.setAuthor("Elvin Zeng");
editor.setContent("第一个版本");
versionManager.add(editor.snapshoot());
editor.setAuthor("Elvin Zeng");
editor.setContent("第二个版本");
versionManager.add(editor.snapshoot());
editor.setAuthor("zenghui");
editor.setContent("第三个版本");
System.out.println("编辑器当前状态:" + editor);
System.out.println("撤回上一个版本");
editor.revertSnapshot(versionManager.pop()); // 可以通过Ctrl+Z热键触发这段代码
System.out.println("编辑器当前状态:" + editor);
System.out.println("撤回上一个版本");
editor.revertSnapshot(versionManager.pop());
System.out.println("编辑器当前状态:" + editor);
}
}
上面的这些操作在实际程序中可以监听键盘事件去触发,这样用起来会更自然一些。
执行结果
$ java -jar memento-1.0-SNAPSHOT.jar
编辑器当前状态:Editor{author='zenghui', content='第三个版本'}
撤回上一个版本
编辑器当前状态:Editor{author='Elvin Zeng', content='第二个版本'}
撤回上一个版本
编辑器当前状态:Editor{author='Elvin Zeng', content='第一个版本'}
备忘录模式分析
备忘录模式几乎可以理解为是专为解决本文例子中的这类问题而存在的。想明白了这个模式的意图之后,也能反过来思考问什么这个模式需要这三个角色。 Originator 是这个模式应用的主要对象,很明显是需要的。Memento 则是用于存储Originator的状态的对象,也是需要的。 这个Memento可以理解为是Originator的某个时刻的一个“快照”对象。用大白话说就是Memento只是一个模型对象, 我们将Originator的字段值保存在Memento中,Memento 只是Originator的属性在某个时刻的备份的容器。 而Caretaker则是保管这一堆“快照”的管理对象,它负责存储。
另外,本文的例子只是一个非常简单的实现。实际程序中,我们可能会遇到另一个问题——Originator 需要保存的字段过多。这个问题的话,就是编程技巧的问题了。
比如,让Memento持有一个Map
,然后通过反射自动保存所有的属性。再比如直接用apache的BeanUtils
。具体处理方法在这里我就不废话了。╭(′▽`)╯
参考
后记
最近一段时间感觉有点沮丧。废了很大的功夫,利用自己的业余时间坚持去写作,但是却并没有几个人看。
幸好,今天收到了来自她的鼓励。。
现在我想起来一句话:
一个人至少拥有一个梦想,有一个理由去坚强,心若没有栖息的地方到哪里都是流浪。 ~三毛
设计模式,其实我在很多年前就已经学过了。大二的时候吧,掐指一算也有四五年了。有的小伙伴建议我直接从更高大上的话题开始,这样或许会有更多人看,但是我没有采纳他们的建议。
因为我明白我真正想要的是什么。我现在所做的,其实仅仅只是在回顾。人生并不是一场表演,为了下一阶段能有一个更扎实的基础,我决心从基本功开始。外功易得,内功难修..
设计模式系列文章,二十多篇,还差几篇就要写完了。再坚持一下!写完这些基础话题,再让我来装逼。。。
(^-^)V