(目录)
相较于其他设计模式,备忘录模式不算太常用,但好在这个模式理解、掌握起来并不难,代码实现也比较简单,应用场景更是比较明确和有限。 一般应用于编辑器或会话上下文中防丢失、撤销、恢复等场景中。
模式原理分析
备忘录模式的原始定义是:
捕获并外部化对象的内部状态,以便以后可以恢复,所有这些都不会违反封装。
这个定义是非常简单的,备忘录模式对应的 UML 图:
从上面 UML 图中,备忘录模式包含两个关键角色。
原始对象(Originator)
:
- 创建自身所需要的属性和业务逻辑;
- 提供方法 create() 和 restore(memento) 来保存和恢复对象副本。
备忘录(Memento)
:用于保存原始对象所有属性状态,以便在未来进行撤销操作。
首先,创建原始对象 Originator
,对象中有四个属性,分别:
- state 用于显示当前对象状态;
- id、name、phone 用来模拟业务属性;
- 并添加 get、set 方法、
create() 方法用于创建备份对象,restore(memento) 用于恢复对象状态
。
public class Originator {
private String state = "原始对象";
private String id;
private String name;
private String phone;
public Originator(){}
public Memento create() {
return new Memento(id, name, phone);
}
public void restore(Memento m) {
this.state = m.getState();
this.id = m.getId();
this.name = m.getName();
this.phone = m.getPhone();
}
@Override
public String toString() {
return "Originator{" +
"state='" + state + '\'' +
", id='" + id + '\'' +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
然后,再来创建备忘录对象 Memento,备忘录对象几乎就和原始对象的属性一模一样。
public class Memento {
private String state = "从备份对象恢复为原始对象"; // 打印当前状态
private String id;
private String name;
private String phone;
public Memento(String id, String name, String phone) {
this.state = state;
this.id = id;
this.name = name;
this.phone = phone;
}
@Override
public String toString() {
return "Memento{" +
"state='" + state + '\'' +
", id='" + id + '\'' +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
接着运行一个单元测试,如下代码:
package cn.happymaya.ndp.behavioral.memento;
public class Demo {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setId("1");
originator.setName("mickjoust");
originator.setPhone("12345678900");
System.out.println(originator);
Memento memento = originator.create();
originator.setName("修改");
System.out.println(originator);
originator.restore(memento);
System.out.println(originator);
}
}
// 输出结果
// Originator{state='原始对象', id='1', name='mickjoust', phone='12345678900'}
// Originator{state='原始对象', id='1', name='修改', phone='12345678900'}
// Originator{state='从备份对象恢复为原始对象', id='1', name='mickjoust', phone='12345678900'}
从上面的代码实现和最后输出的结果可以看出,备忘录模式代码实现非常简单的,关键点就在于要能保证原始对象在某一个时刻的对象状态被完整记录下来。
使用场景
备忘录模式经常使用的场景以下两种:
- 需要保存一个对象在某一个时刻状态时。比如,在线编辑器中编写文字时断网,需要恢复为断网前的状态;
- 不希望外界直接访问对象的内部状态时。比如,在包裹配送的过程中,如果从仓库运送到配送站,只需要显示“在运行中”,而具体使用汽车还是飞机,这个用户并不需要知道。
通过一个栗子演示一下,假设现在正在管理一个博客系统,经常需要创建 Blog 对象,但是有一些作者在写 Blog 的过程中可能会出现断网的情况,这时就需要保存 Blog 对象在这个时刻的状态信息,后续等作者重新联网后,能够及时地恢复其断网前的状态。那具体该怎么来做呢? 首先,创建一个 Blog 对象,该对象中包含 id、title 和 content,分别代表了 Blog 的唯一编号、标题和内容;并提供创建备忘录的 createMemento() 和 restore(BlogMemento m) 方法,分别用于创建备忘录和通过备忘录来恢复原始的 Blog 对象。
public class Blog {
private Long id;
private String title;
private String content;
public Blog(Long id, String title) {
this.id = id;
this.title = title;
}
// 省略 getter/setter
public BlogMemento createMemento() {
return new BlogMemento(id, title, content);
}
public void restore(BlogMemento m) {
this.id = m.getId();
this.title = m.getTitle();
this.content = m.getContent();
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}
然后,创建一个 Blog 的备忘录对象 BlogMemento,同样是复制 Blog 所需要的所有属性内容。
public class BlogMemento {
private final long id;
private final String title;
private final String content;
public BlogMemento(long id, String title, String content) {
this.id = id;
this.title = title;
this.content = content;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
这样基于 Blog 对象的备忘录就创建好了。最后,依然运行一段单元测试代码来看看运行结果。
package cn.happymaya.ndp.behavioral.memento;
public class Client {
public static void main(String[] args) {
Blog blog = new Blog(1L, "My Blog");
blog.setContent("ABC"); // 原始文章
System.out.println(blog);
BlogMemento memento = blog.createMemento(); // 创建 blog 的备忘录
blog.setContent("123"); // 改变内容
System.out.println(blog);
blog.restore(memento); // 撤销操作
System.out.println(blog); // 这时显示原始的内容
}
}
// 输出结果
Blog{id=1, title='My Blog', content='ABC'}
Blog{id=1, title='My Blog', content='123'}
Blog{id=1, title='My Blog', content='ABC'}
从运行结果中,可以发现使用备忘录模式能非常方便地进行撤销操作。编辑文章内容时,其实就是在修改 content 内容,这时备忘录会记录特定时间点里的对象状态,如果这时需要撤销修改,那么就会恢复到原来的对象状态。所以说,备忘录模式在频繁需要撤销与恢复的场景中能够发挥很好的作用。
备忘录模式
使用备忘录模式的原因,可总结为以下两个。
- **为了记录多个时间点的备份数据。**与传统备份不同的是,备忘录模式更多是用来记录多个时间点的对象状态数据,比如编辑器、聊天会话中会涉及多次操作和多次交互对话,一方面是为了记录某个时间点数据以便以后运营用来做数据分析,另一方面是为了能够通过多次数据快照,防止客户端篡改数据;
- 需要快速撤销当前操作并恢复到某对象状态。 比如,微信中的撤回功能其实就是备忘录模式的一种体现,用户发错信息后,需要立即恢复到未发送状态。
优缺点
通过上述分析,备忘录模式,主要有以下优点:
- 能够快速撤消对对象状态的更改。 比如,在编辑器中不小心覆盖了一段重要的文字,使用 undo 操作能够快速恢复这段文字;
- 能够帮助缓存记录历史对象状态。比如,在客服会话聊天中,对于某一些重要的对话(退换货、价保等),我们会记录这些对象数据,传统的做法是调用一次服务接口,一旦服务出现故障就很容易导致聊天回复速度变慢;而使用备忘录模式则能够记录这些重要的数据信息(用户提供的订单数据),而不需要反复查询接口,这样能提升回复客户的效率。
- 能够提升代码的扩展性。 备忘录模式是通过增加外部对象来保存原始对象的状态,而不是在原始对象中新增状态记录,当不再需要保存对象状态时就能很方便地取消这个对象。同理,新增备忘录对象也非常容易。
同样,备忘录模式也有一些缺点:
- 备忘录会破坏封装性。 因为当备忘录在进行恢复的过程中遇见错误时,可能会恢复错误的状态。比如,备份的对象状态中有需要调用数据库等外部服务时,在恢复过程中如果遇见数据库宕机,那么可能恢复的对象数据就会存在错误;
- 备忘录的对象数据很大时,读取数据可能出现内存用尽的情况。 比如,在编辑器中加入高清的图片,如果直接记录图片本身可能会导致内存被用尽,进而导致系统出现崩溃的情况。
总结
备忘录模式也叫快照模式。具体来说,就是通过捕获对象在某一个时刻对象状态,再将其保存到外部对象,以便在需要的时候恢复对象到指定时刻下的状态。 **备忘录模式的应用场景比较局限,主要是用来备份、撤销、恢复等,**这与平时常说的“备份”看上去很相似,但实际上两者的差异是很大的:
- 备忘录模式更侧重于代码设计和实现,支持简单场景中的应用,比如记录 Web 请求中的 header 信息等;
- 备份更多是以解决方案的形式出现,比如异地容灾备份、数据库主从备份等所支持的是更复杂的业务场景,备份的数据量往往也更大。
因此,在使用备忘录模式时,莫要认为它就是万能的备份模式,要合理评估对象所使用的内存空间,再确定是否使用备忘录模式。
标签:String,备忘录,对象,public,Blog,会话,设计模式,id,Memento From: https://blog.51cto.com/panyujie/7438168