前言
文章内容主要参考了刘伟主编的《设计模式(第2版)》,同时也结合了自己的一些思考和理解,希望能帮到大家。
本篇讲解备忘录模式。其实非常常见,就是我们常用的撤销模式。
当我们想让系统撤销回退到系统的某一个历史状态,就需要撤销功能,要能实现撤销就必须有所保存历史状态,然后进行覆盖。
正文
一、定义
备忘录模式(Memento Pattern)定义:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为模式,起别名Token。
这个模式就是提供了一种状态恢复的机制,现在很多撤销功能都是利用了备忘录模式。
模式中共有三个角色:1. 原发器(Originator):可以理解就是当前使用的对象,只不过在这个对象中有提供两个特殊方法(拍下当前对象的快照并返回,其次就是将某一个历史快照进行恢复)。2. 备忘录(Memento):这个其实就可以说是对象的空壳,当我们要保存历史状态时,就是将快照放置在这个载体上,而其实备忘录和原发器时非常相像的只是个别方法不同。3. 负责人(Craetaker):就是存储器,用来存储快照的。
其中备忘录类只面向原发器类,因为我们存储的快照载体就是备忘录类,如果这其中的接口对外暴露,这历史状态就有可能改变,那么就违背了我们撤销的目的。而实现这样的方式有两种。
1.将原发器和备忘录放在同一包下,同时设置备忘录为默认可见性,这时只有同一个包下才可以访问备忘录。
2.将备忘录作为原发器的内部类。
二、情景假设
某系统提供了用户信息操作模块,用户可以修改自己的各项信息。为了使操作过程更加的人性化,现使用备忘录模式对系统进行改进,使得用户在进行了错误操作之后可以恢复到操作之前的状态。
三、情景分析
关于上面情景的类图(具体分析在下面)
左边的UserInfoDTO就是用户类,右边的Memento就是备忘录就是一个载体,你会发现他们两者是非常相像的。下面的Caretaker就是存储状态的容器。
原发器UserInfoDTO(用户信息类)
public class UserInfoDTO {
private String account;
private String password;
private String telNo;
//获取此时的快照,利用备忘录类作为载体返回
public Memento saveMemento(){
return new Memento(account, password, telNo);
}
//将历史记录快照进行恢复出来
public void restoreMemento(Memento memento){
this.account = memento.getAccount();
this.password = memento.getPassword();
this.telNo = memento.getTelNo();
}
public void show(){
System.out.println("Account:" + this.account);
System.out.println("Password:" + this.password);
System.out.println("TelNo:" + this.telNo);
}
下面是三个属性的setter、getter这里不多展示
}
备忘录类Memento
!!!!!这里是没有public的,采用默认的可见符,即包内可见!!!!
class Memento {
private String account;
private String password;
private String telNo;
下面是三个属性构成的有参构造函数,getter、setter,这里不多展示
}
负责人类,就是容器,并且提供getter、setter
public class Caretaker {
private Memento memento;//容器存储历史状态
public Memento getMemento(){
return memento;
}
public void setMemento(Memento memento){
this.memento = memento;
}
}
接下来是客户端的代码:
public class Client {
public static void main(String[] args) {
UserInfoDTO user = new UserInfoDTO();
Caretaker c = new Caretaker();
user.setAccount("张三");
user.setPassword("123456");
user.setTelNo("13000000000");
System.out.println("状态一:");
user.show();
c.setMemento(user.saveMemento());//存储状态
System.out.println("-----------------------");
user.setPassword("654321");
user.setTelNo("14000000000");
System.out.println("状态二:");
user.show();
System.out.println("-----------------------");
user.restoreMemento(c.getMemento());//恢复状态
System.out.println("回到状态一:");
user.show();
}
}
以上就是备忘录模式的具体实现。其中我们的备忘录类采用了默认的包内可见性方式。历史状态的保存、恢复和存储都需要负责人类来执行。原发器类则提供快照。
四、 模式分析
模式类图:
- Originator:原发器类
- Memento:备忘录类
- Caretaker:负责人类
(1) 模式特点
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。
- 实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其他代码所改动。
(2) 模式缺点
- 资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存,而且每保存一次对象的状态都需要小号内存资源。
五、使用情景
- 保存一个对象在某一个时刻的状态或部分状态,这样以后需要它时能够恢复到先前的状态。
六、拓展与延申
- 备忘录的封装性
就是原发器可以访问备忘录类外其他不应该访问,所以在c++可以使用friend关键字,java可以将两个类放入一个包中,使他们之间可以满足默认的包可见性,也可以将备忘录类作为原发器的内部类。 - 多备份的实现
很多时候,负责人保存的状态不止一个的,所以对于备忘录类,可以设置一个集合容器存储多个历史状态。比如HashMap、ArrayList和栈,但有时候我们想撤回的时候甚至可以不想撤回了回去没有撤回的状态,大家加可以想想是什么数据结构啦! - 备忘录模式其实害可以有另一种实现方式:我们将容器存储不单独成一个类,而是做一个容器放置在类属性,当我们需要存储历史状态时,我们可以通过克隆添加容器内!这样可能开发更为容易,但是有个问题就是克隆其实是整个对象整体克隆,就会占内存。
总结
本篇文章主要介绍设计模式的备忘录模式,也就是我们的撤回实现!