设计模式之备忘录模式

备忘录模式的定义

在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

备忘录模式也叫快照模式,提供了一种类似后悔药的机制。

举个栗子

备忘录模式的模型图

备忘录模式有三种角色

  • 发起人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMemento(){
return new Memento(state);
}
public void restoreMemento(Memento memento){
setState(memento.getState());
}
}
  • 备忘录
1
2
3
4
5
6
7
8
9
10
11
12
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
  • 备忘录管理员
1
2
3
4
5
6
7
8
9
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
  • 调用场景
1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.restoreMemento(caretaker.getMemento());
}
}

这样就完成了一个备忘录的功能,这是最基本的实现方式。

适用范围

备忘录模式适用于需要撤销回滚的动作,在实际开发中很常见,比如数据库连接中的事务处理。

优点缺点

备忘录模式的优点可以提供了一个版本的自动化管理机制,可以很方便地回退。

备忘录模式的缺点在于有时候为了备份会浪费资源,而且创建者无法知道究竟会浪费多少资源。

注意事项

使用备忘录一定要注意备忘录的生命周期,还要避免创建过多的备忘录,不要给系统带来过分的不必要的开销。

备忘录模式的变形

除了基本的方式,备忘录模式还有很多特殊的实现。

clone方式的备忘录

我们可以利用原型模式,通过克隆的方式创建备忘录,模型如下

  • 发起人和备忘录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Originator implements Cloneable {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Originator createMemento() {
return clone();
}
public void restoreMemento(Originator originator) {
setState(originator.getState());
}
@Override
protected Originator clone() {
try {
return (Originator) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
  • 备忘录管理员
1
2
3
4
5
6
7
8
9
public class Caretaker {
private Originator originator;
public Originator getOriginator() {
return originator;
}
public void setOriginator(Originator originator) {
this.originator = originator;
}
}

不过这种方式还可以继续简化,因为此时已经没有了独立的备忘录角色,所以管理员其实也可以由发起人自身管理,此时就变成一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Originator implements Cloneable {
private Originator backup;
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public void createMemento() {
backup = clone();
}
public void restoreMemento() {
setState(backup.getState());
}
@Override
protected Originator clone() {
try {
return (Originator) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}

调用代码很简单,就不贴出来了。这种方式和概念不太相符,它不是在对象外部保存,而是把所有逻辑都移到内部,这种方式适用于简单的场景,如果情况复杂考虑到深拷贝等问题尽量不要使用。

多状态的备忘录模式

对于多状态的备忘录模式,我们可以采取装配到bean中的方式

  • 装配bean的工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class BeanUtils {
public static HashMap<String,Object> backProp(Object bean){
HashMap<String,Object> result=new HashMap<>();
try {
BeanInfo beanInfo= Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] descriptors=beanInfo.getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
String fieldName=descriptor.getName();
Method getter=descriptor.getReadMethod();
Object fieldValue=getter.invoke(bean,new Object[]{});
if (!fieldName.equalsIgnoreCase("class")){
result.put(fieldName,fieldValue);
}
}
}catch (Exception e){
//异常处理
}
return result;
}
public static void restoreProp(Object bean,HashMap<String,Object> propMap){
try {
BeanInfo beanInfo=Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] descriptors=beanInfo.getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
String fieldName=descriptor.getName();
if (propMap.containsKey(fieldName)){
Method setter=descriptor.getWriteMethod();
setter.invoke(bean,new Object[]{propMap.get(fieldName)});
}
}
} catch (Exception e) {
//异常处理
e.printStackTrace();
}
}
}
  • 发起人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Originator {
private String state1;
private String state2;
private String state3;
public String getState1() {
return state1;
}
public void setState1(String state1) {
this.state1 = state1;
}
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
public String getState3() {
return state3;
}
public void setState3(String state3) {
this.state3 = state3;
}
public Memento createMemento(){
return new Memento(BeanUtils.backProp(this));
}
public void restoreMemento(Memento memento){
BeanUtils.restoreProp(this,memento.getStateMap());
}
}
  • 备忘录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Memento {
private HashMap<String,Object> stateMap;
public Memento(HashMap<String, Object> stateMap) {
this.stateMap = stateMap;
}
public HashMap<String, Object> getStateMap() {
return stateMap;
}
public void setStateMap(HashMap<String, Object> stateMap) {
this.stateMap = stateMap;
}
}

管理员类代码不变,这种方式处理之后就可以随意增加多个状态,调用比较简单这里不贴代码了。

多备份的备忘录

如果我们需要创建多份备忘录,上面的又无法满足条件了,这时候修改管理员代码如下

1
2
3
4
5
6
7
8
9
public class Caretaker {
private HashMap<String, Memento> memMap = new HashMap<>();
public Memento getMemento(String idx) {
return memMap.get(idx);
}
public void setMemento(String idx, Memento memento) {
this.memMap.put(idx, memento);
}
}

这种方式要注意,创建的备份不能自动销毁,所以要限制map上限,防止内存溢出。

使用内部类的备忘录

要保证备份数据安全,防止备忘录被修改,我们可以把备忘录设置成发起角色的内部类,直接贴代码

  • 发起人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMemento(){
return new Memento(this.state);
}
public void restoreMemento(MementoInterface memento){
setState(((Memento)memento).getState());
}
private class Memento implements MementoInterface{
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
}
  • 备忘录空接口
1
2
public interface MementoInterface {
}
  • 备忘录管理员
1
2
3
4
5
6
7
8
9
public class Caretaker {
private MementoInterface memento;
public MementoInterface getMemento() {
return memento;
}
public void setMemento(MementoInterface memento) {
this.memento = memento;
}
}

这种方式通过接口建立联系,采用内部类实现,备忘录更加安全。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!