行为型模式
目录
1、备忘录模式
备忘录这个词汇大家应该都不陌生,我就经常使用备忘录来记录一些比较重要的或者容易遗忘的信息,与之相关的最常见的应用有许多,比如游戏存档,我们玩游戏的时候肯定有存档功能,旨在下一次登录游戏时可以从上次退出的地方继续游戏,或者对复活点进行存档,如果挂掉了则可以读取复活点的存档信息重新开始。与之相类似的就是数据库的事务回滚,或者重做日志redo log等。
- 备忘录模式(Memento),在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存着这个状态。这样以后就可将该对象恢复到原先保存的状态。
- 意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- 主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
- 何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
- 如何解决:通过一个备忘录类专门存储对象状态。
- 关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。
1.1 备忘录模式UML图
1.2 日常生活中看备忘录模式与应用实例
开头中提到的游戏进度保存。
应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。
1.3 Java代码实现
备忘录模式又可以分为“白箱”备忘录模式和“黑箱”备忘录模式。
“白箱”备忘录模式
Java中,实现“宽”和“窄”两个接口并不容易,如果暂时忽略两个接口的区别,仅为备忘录角色提供一个宽接口的话,备忘录的内部存储状态就对所有对象公开,这就是“白箱实现”。
“白箱”实现破坏了封装性,但是通过程序员自律,可以方便地实现备忘录模式。“白箱”备忘录模式的Java实现如下:
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
}
public class Originator {
private String state;
//工厂方法,返回一个新的备忘录对象
public Memento createMemento() {
return new Memento(state);
}
//将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
public String getState() {
System.out.println("Current state:" + state);
return this.state;
}
public void setState(String state) {
this.state = state;
}
}
public class Caretaker {
private Memento memento;
public Memento retrieveMemento() {
return this.memento;
}
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
//改变发起人的状态
originator.setState("on");
originator.getState();
//创建备忘录对象,并将发起人对象的状态存储起来
caretaker.saveMemento(originator.createMemento());
//再次改变发起人对象的状态
originator.setState("off");
originator.getState();
//恢复发起人对象的状态
originator.restoreMemento(caretaker.retrieveMemento());
originator.getState();
}
}
“黑箱”备忘录模式
“黑箱”备忘录模式相比“白箱”备忘录模式有如下区别:
1.将Memento设成Originator类的内部类;
2.将Memento的方法全部设成私有方法,这样只有它自己和发起人Originator可以调用;
3.在外部提供一个标识接口MementoIF给Caretaker以及其他对象,标识接口MementoIF没有提供任何方法,因此对外部来说Memento对象的内容都是不可见的。
//标识接口(窄接口)
public interface MementoIF {
}
public class Originator2 {
private Vector<Object> states;
private int index;
public Originator2() {
states = new Vector<Object>();
index = 0;
}
//工厂方法,返回一个新的备忘录对象
public MementoIF createMemento() {
return new Memento(this.states, index);
}
//将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(MementoIF memento) {
states = ((Memento) memento).getStates();
index = ((Memento) memento).getIndex();
}
//状态的赋值方法
public void setState(Object state) {
this.states.addElement(state);
index++;
}
//辅助方法,打印出所有状态
public void printStates() {
System.out.println("Total number of states:" + index);
for (Object o : states) {
System.out.println(o.toString());
}
}
//内部类
protected class Memento implements MementoIF {
private Vector<Object> saveStates;
private int saveIndex;
@SuppressWarnings("unchecked")
//_states一定是Vector<Object类型的变量,复制后也一定是Vector<Object的变量
private Memento(Vector<Object> _states, int _index) {
//保存客户端传来的状态对象的拷贝,否则客户端的修改会影响到保存的状态。
saveStates = (Vector<Object>) _states.clone();
saveIndex = _index;
}
private Vector<Object> getStates() {
return saveStates;
}
private int getIndex() {
return saveIndex;
}
}
}
public class Caretaker2 {
private Originator2 o;
private Vector<MementoIF> mementos = new Vector<MementoIF>();
private int currentIndex;
public Caretaker2(Originator2 o) {
this.o = o;
currentIndex = 0;
}
//创建一个新的检查点
public void createMemento() {
mementos.addElement(o.createMemento());
currentIndex++;
}
//将发起人恢复到某个检查点
public void reStoreMemento(int index) {
o.restoreMemento(mementos.elementAt(index));
}
//删除某个检查点
public void removeMemento(int index) {
mementos.removeElementAt(index);
}
}
public class Client2 {
public static void main(String[] args) {
Originator2 o = new Originator2();
Caretaker2 c = new Caretaker2(o);
//改变发起人的状态
o.setState("state0");
//创建一个检查点
c.createMemento();
o.setState("state1");
c.createMemento();
o.setState("state2");
c.createMemento();
o.setState("state3");
c.createMemento();
o.setState("state4");
c.createMemento();
//打印出所有状态
o.printStates();
//恢复到第3个检查点
System.out.println("Restoring to 3");
c.reStoreMemento(3);
o.printStates();
//恢复到第0个检查点
System.out.println("Restoring to 0");
c.reStoreMemento(0);
o.printStates();
//恢复到第4个检查点
System.out.println("Restoring to 4");
c.reStoreMemento(4);
o.printStates();
}
}
跟例子1相比,负责人角色除了负责保存状态之外,还负责发起人状态的恢复,功能增强了。
总结一下:
- “黑箱”备忘录的实现中,将Memento类做成Originator的内部类,并将其方法全部设置成private,其实这样一般来说就已经足够了,不需要再使用窄接口MementoIF。因为这样做的话外部拿到Memento类的实例,由于其方法都是private的,所以该方法只有Originator类可以调用,其它类是调用不了的,也就无法修改其中的内容。
- 那么窄接口什么时候使用呢,我觉得应该是这样,如果Memento类因为某些原因不能做成内部类,那么就应该定义两个接口,一个WideMemento,一个NarrowMemento(一般没有定义任何方法),前者供Originator类使用,后者供其它类使用。这样的缺点就是,外部只要将得到的实例强制转化为WideMemento类型,同样可以访问到Memento类的内容。
- 如果要继承Originator类并且不改变Memento类的代码,那么Memento类的方法应该设置成默认属性(package access),而不是private。
2、备忘录模式Spring源码中的提现
引入jar包
类StateManageableMessageContext中
3、备忘录模式优缺点
3.1 优点
- 如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。
- 备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用先前存储起来的备忘录将状态复原。
- 实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其他代码改动,这种模式简化了原发器对象,备忘录只保存原发器的状态,采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在负责人中定义集合对象来存储多个备忘录。
- 本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需要的这些状态的版本。
- 当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
3.2 缺点
- 在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存,而且每保存一次对象的状态都需要消耗内存资源,如果知道这一点大家就容易理解为什么一些提供了撤销功能的软件在运行时所需的内存和硬盘空间比较大了。
- 如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
- 当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。
- 当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
3.3 使用场景
- 提供一个可回滚的操作。
- 如果系统需要提供回滚操作时,使用备忘录模式非常合适。例如文本编辑器的Ctrl+Z撤销操作的实现,数据库中事务操作。
- 保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态。
- 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过负责人可以间接访问其内部状态。
- 有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。
3.4 注意事项
- 为了符合迪米特原则,还要增加一个管理备忘录的类。
- 为了节约内存,可使用原型模式+备忘录模式。
3.5 多备份实现
- 在负责人中定义一个集合对象来存储多个状态,而且可以方便地返回到某一历史状态。
- 在备份对象时可以做一些记号,这些记号称为检查点(Check Point)。在使用HashMap等实现时可以使用Key来设置检查点。
参考文章:
https://blog.csdn.net/u012124438/article/details/70473553