xyZGHio

本是青灯不归客,却因浊酒恋风尘

0%

GoF设计模式(十八):Memento Pattern 备忘录模式

Memento Pattern 备忘录模式,可以让我们对某时刻对象的内部状态进行保存、备份,以便日后需要时对其进行恢复、还原

abstract.jpeg

模式思想

我们日常工作、生活中也有很多类似的需求、应用。比如Word文档的撤销功能、玩游戏时的存档功能等。而备忘录模式即是对上述需求、场景、应用的经验总结、理论提炼。具体地,在该模式下有三个角色

  • 发起人角色:通常我们将需要进行备份保存内部状态、数据的对象,称之为发起人角色。其支持创建某个时候该角色内部状态的数据备份(即所谓的备忘录)及利用某个数据备份(即所谓的备忘录)来恢复该角色的内部状态。即本文示例的Role游戏角色类
  • 备忘录角色:该角色作用在于保存发起人角色的状态、数据。即本文示例的GameArchive游戏存档类
  • 负责人角色:该角色提供了对备忘录实例的管理功能。具体地,包括存储、提供备忘录实例等功能。即本文示例的GameArchiveCaretaker游戏存档的负责人类

在运行过程中,发起人根据需要适时创建某个时刻下数据、状态的备份,即创建备忘录角色实例。并将该备忘录实例交由负责人角色进行存储、管理。当需要恢复备份时,则先从负责人角色获取相应的备忘录实例,然后交由发起人角色以用于数据、状态的恢复

实现

现在我们来实现一个游戏存档的小例子,以便大家更好的理解该模式。首先对于一个游戏中的角色而言,其通常具备以下状态:游戏角色名称、关卡名称、生命值、魔法值。与此同时,我们也需要根据该类的状态、数据做备份,即所谓的游戏存档。所以该Role类即为我们的发起人角色。可以看到在该类实现中,我们分别提供了createGameArchive方法、loadGameArchive方法,实现了对游戏过程的存档、读档功能

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 发起人角色: 游戏角色
*/
@Data // 可提供各字段的get、set方法
public class Role {
/**
* 游戏角色姓名
*/
private String roleName;

/**
* 游戏关卡名称
*/
private String missionName;

/**
* 生命值
*/
private Integer hp;

/**
* 魔法值
*/
private Integer mp;

public Role(String roleName, String missionName, Integer hp, Integer mp) {
this.roleName = roleName;
this.missionName = missionName;
this.hp = hp;
this.mp = mp;
}

/**
* 获取当前游戏状态信息
* @return
*/
public void getInfo() {
String info1 = "游戏状态: <"+ roleName +"> 在 " + missionName;
String info2 = " 生命值: " + hp + " 魔法值: " + mp + "\n";
System.out.println(info1);
System.out.println(info2);
}

/**
* 创建游戏存档
* @return
*/
public GameArchive createGameArchive() {
GameArchive gameArchive = new GameArchive(roleName, missionName, hp, mp);
return gameArchive;
}

/**
* 加载游戏存档
* @param gameArchive
*/
public void loadGameArchive(GameArchive gameArchive) {
this.roleName = gameArchive.getRoleName();
this.missionName = gameArchive.getMissionName();
this.hp = gameArchive.getHp();
this.mp = gameArchive.getMp();
}
}

现在让我们来实现备忘录角色,即所谓的游戏存档GameArchive类。其用于保存游戏过程中某个时刻游戏角色的状态数据。其实现如下所示

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
/**
* 备忘录角色:游戏存档
*/
@Getter // 可提供各字段的get方法
public class GameArchive {
/**
* 游戏角色姓名
*/
private String roleName;

/**
* 游戏关卡名称
*/
private String missionName;

/**
* 生命值
*/
private Integer hp;

/**
* 魔法值
*/
private Integer mp;

public GameArchive(String roleName, String missionName, Integer hp, Integer mp) {
this.roleName = roleName;
this.missionName = missionName;
this.hp = hp;
this.mp = mp;
}

}

最后,我们来实现负责人角色,即游戏存档负责人GameArchiveCaretaker类。其作用就是提供对游戏存档的管理功能。这里我们通过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
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 负责人角色: 游戏存档的负责人
*/
public class GameArchiveCaretaker {

/**
* key: 存档名称; value: 相应游戏存档
*/
private Map<String, GameArchive> map = new HashMap<>();

/**
* 添加游戏存档
* @param gameArchive
*/
public void addGameArchive(GameArchive gameArchive) {
// 使用游戏角色名称、游戏关卡名称作为游戏存档的名称
String gameArchiveName = gameArchive.getRoleName() + "-" + gameArchive.getMissionName();
map.put( gameArchiveName, gameArchive );
}

/**
* 根据游戏存档名称获取指定游戏存档
* @param gameArchiveName
* @return
*/
public GameArchive getGameArchive(String gameArchiveName) {
GameArchive gameArchive = map.get(gameArchiveName);
return gameArchive;
}

/**
* 查看游戏存档列表
*/
public void viewGameArhiveNameList() {
Set gameArhiveNameList = map.keySet();
String str = "游戏存档列表: " + gameArhiveNameList.toString() + "\n";
System.out.println(str);
}
}

好了,现在让我们来看看该模式是如何运作的

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
37
/**
* Memento Pattern 备忘录模式 Demo
*/
public class MementoPatternDemo {
public static void main(String[] args) {
// 构造游戏存档负责人, 用于管理游戏存档
GameArchiveCaretaker gameArchiveCaretaker = new GameArchiveCaretaker();
// 创建游戏角色,开始游戏
Role role = new Role("林克", "第一关: 玛雅神庙", 100, 100);
role.getInfo();
// 创建游戏存档并交由负责人管理、存储
gameArchiveCaretaker.addGameArchive( role.createGameArchive() );

// 经过一番激烈战斗,现在来到了第二关
role.setMissionName("第二关: 雷电圣兽");
role.setHp(90);
role.setMp(70);
role.getInfo();
// 创建游戏存档并交由负责人管理、存储
gameArchiveCaretaker.addGameArchive( role.createGameArchive() );

// 查看游戏存档列表
gameArchiveCaretaker.viewGameArhiveNameList();

// 不幸的是, 经过第二关的激烈战斗, 虽然侥幸来到了第三关, 但生命已经岌岌可危
role.setMissionName("第三关: 二河流域");
role.setHp(11);
role.setMp(7);
role.getInfo();

// 这样下去显然无法通关啊, 算了重新读档, 把第二关重新打一次吧
String gameArchiveName = "林克-第二关: 雷电圣兽"; // 游戏存档名称
// 从负责人中获取相应的游戏存档, 并加载恢复回来
role.loadGameArchive( gameArchiveCaretaker.getGameArchive(gameArchiveName) );
role.getInfo();
}
}

测试结果如下,符合预期

figure 1.jpeg

参考文献

  1. Head First 设计模式 弗里曼著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝