前言
在平时使用软件的过程中,难免会碰到误操作的时候,这个时候就需要有一种“回退”机制。软件可以保存用户每一次操作时系统的状态,这样如果出现误操作,就可以将存储的历史状态取出,即回到之前的状态。
比如我们经常使用的Ctrl+z(撤销),就是解决误操作的问题。
备忘录模式
模式定义
在不破坏封装的条件下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后需要的情况下恢复到原来保存的状态。备忘录模式是一种行为型模式,也叫快照模式。
模式结构
备忘录模式中,核心是备忘录类和管理人类的设计。具体的结构图如下:
说明:
Originator:原发器。简单来说就是需要保存内部状态的目标类。它可以创建一个备忘录来保存当前内部状态,也可以通过备忘录来恢复内部状态。
Memento:备忘录,存储原发器的内部状态,根据原发器来决定保存哪些状态。备忘录的设计一般参考原发器的设计,根据实际需要来确定自身属性。注意:除了原发器本身和负责人之外的所有类都不可以使用备忘录对象。
Caretaker:负责人,负责保存备忘录,但是不能对备忘录进行操作或检查。它只负责存储对象,而不能修改对象,也不需要知道具体的对象实现细节。
从结构上来看,备忘录模式并不难,关键在于如何设计备忘录和负责人,从而防止其他无关的类访问备忘录。
模式设计
首先是原发类:
class Originator {
private String state;
public Memento1 saveMemento() {return new Memento1(this);}
public void restore(Memento1 m) {
this.state = m.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
备忘录类:
class Memento1 {
private String state;
public Memento1(Originator o) {
this.state = o.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
class Caretaker {
private Memento1 me;
public Memento1 getMe() {
return me;
}
public void setMe(Memento1 me) {
this.me = me;
}
}
设计的时候要考虑到备忘录只能被原生类访问,否则其他类可以调用备忘录的构造方法或其他函数修改了备忘录中对象的状态,这样通过撤销操作所得到的历史状态也不是真正的历史状态,备忘录模式也就 失去了意义。
在java中只要保证原生类和备忘录类处于一个包中,且备忘录的访问权限是default的即可实现。
举例
案例描述:用户修改信息时,有时候进行了一些错误的操作,设计一个备忘录来支持用户的撤销
方案一
原生类和备忘录类处于同一个包中:
class Memento {
private String Account;
private String passwd;
private String telNo;
public Memento(String account, String passwd, String telNo) {
Account = account;
this.passwd = passwd;
this.telNo = telNo;
}
public String getAccount() {
return Account;
}
public void setAccount(String account) {
Account = account;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getTelNo() {
return telNo;
}
public void setTelNo(String telNo) {
this.telNo = telNo;
}
}
public class UserInfo {
private String Account;
private String passwd;
private String telNo;
public String getAccount() {
return Account;
}
public void setAccount(String account) {
Account = account;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getTelNo() {
return telNo;
}
public void setTelNo(String telNo) {
this.telNo = telNo;
}
public Memento saveMemento() {
return new Memento(this.Account, this.passwd, this.telNo);
}
public void restoreMemento(Memento memento) {
this.Account = memento.getAccount();
this.passwd = memento.getPasswd();
this.telNo = memento.getTelNo();
}
@Override
public String toString() {
return "UserInfo{" +
"Account='" + Account + '\'' +
", passwd='" + passwd + '\'' +
", telNo='" + telNo + '\'' +
'}';
}
}
负责人类:
public class CareTaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
这是最简单的解决方案,但是存在一个问题每次只能回退到上一个状态,使用的场景比较局限。那么我们要能够存储多个状态的话,便在负责人类中修改一下就可以了。
方案二
其他的不做改变,修改负责人类:
public class CareTaker {
private Memento memento;
ArrayList<Memento> list = new ArrayList<>();
public Memento getMemento(int index) {//index表示末尾的元素
return list.get(index);
}
public void setMemento(Memento memento) {
list.add(memento);
}
}
其次便在进行操作的时候进行一些改进便可,我们来展示一下方案二的一个实例吧
方案二的客户端类:
public class MeClient {
public static CareTaker careTaker = new CareTaker();
public static int index = -1;
public static void main(String[] args) {
UserInfo info = new UserInfo();
info.setAccount("1234556");
info.setPasswd("111111");
info.setTelNo("1787675979");
change(info);
info.setAccount("uuuuuuuuuu");
info.setPasswd("000000000");
change(info);
undo(info);
redo(info);
}
public static void change(UserInfo info) {//改变状态
careTaker.setMemento(info.saveMemento());
index++;
System.out.println(info.toString());
}
public static void undo(UserInfo info) {//撤销
info.restoreMemento(careTaker.getMemento(index-1));
index--;
System.out.println(info.toString());
}
public static void redo(UserInfo info) {//取消撤销
info.restoreMemento(careTaker.getMemento(index + 1));
index++;
System.out.println(info.toString());
}
}
运行结果入下:
set:
UserInfo{Account='1234556', passwd='111111', telNo='1787675979'}、
change:
UserInfo{Account='uuuuuuuuuu', passwd='000000000', telNo='1787675979'}
undo:
UserInfo{Account='1234556', passwd='111111', telNo='1787675979'}
redo:
UserInfo{Account='uuuuuuuuuu', passwd='000000000', telNo='1787675979'}
优缺点
优点:
- 提供了一个状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤。
- 备忘录实现了信息的封装,是一种原生对象状态的表示,不会被其代码修改。可以采用列表堆栈来实现多次撤销。
缺点:
- 资源消耗过大,如果原生类的内部对象和属性太多,则要占用大量的存储空间。
适用场景
- 保存对象在某一个时刻的全部或部分状态,这样可以在以后某个时间恢复到原先的状态,实现撤销操作。
- 防止外界对象破坏一个历史对象状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
常见的使用场景:
- 几乎所有的文字编辑工具和图像编辑工具都有撤销的功能。
- 数据库事务的回滚
模式扩展
备忘录模式+命令模式
- 可以实现命令模式中命令的撤销
备忘录模式+原型模式(点击查看原型模式)
- 在原发类要保存全部或大部分状态时,使用原型模式直接克隆一个对象实例,可以提高效率。也就是说这个时候放在备忘录对象里的是一个原发器对象实例。