配套视频课程已更新完毕,大家可通过以下两种方式观看视频讲解:

关注公众号:爱编程的大丙,或者进入大丙课堂学习。


1. 抹除奥哈拉

在海贼世界中,大约800年前,存在着一个繁荣一时的“巨大王国”,该王国在败给了世界政府联军后便不复存在,因此也留下了一段“空白一百年”的历史。三叶草博士认为,这个国家在败给世界政府的联军之前,就已经做好的失败的思想准备,为了把思想留给未来,于是把所有真相都刻在了石头上,也是就历史正文

22年前,世界政府发现奥哈拉的考古学者们在对历史正文进行研究,于是派人前去逮捕他们。随后,CP9长官斯潘达因对奥哈拉发动了屠魔令,整座岛屿仅妮可·罗宾一人逃走 。翌年,“奥哈拉”这个名字已从地图上消失。

img

其实历史正文就是对空白的一百年历史的记录,我们可以认为这就是一份备忘录,解读出了历史正文就等于还原了历史,里边肯定记录着世界政府干过的一些见不得光的事情,不得不说“巨大王国”的人们还是很聪明的。

在设计模式中也有一种备份数据的模式叫做备忘录模式,关于这种模式的定义是这样的在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后在需要的时候就可以将该对象恢复到原先保存的状态。

关于备忘录模式在现实生活场景中也比较常用,比如:

  • 游戏的进度存档

  • 文本编辑器

    在编辑器中撤销操作

  • 各种经典著作,比如:资治通鉴,史记、二十四史、黄帝内经、论语、道德经

  • 数据库的事务(出现错误时可以进行数据的回滚操作)

2. 勿忘历史

2.1 佛狸祠

海贼中的世界政府消灭了巨大王国,又想要毁灭其历史(要杀死所有能读懂历史正文的历史学家),让全世界的人们都认为世界政府就是救世主,就是绝对的正义。在我们真实世界中,这种事情比比皆是,在辛弃疾的《永遇乐·京口北固亭怀古》中写道可堪回首,佛狸祠下,一片神鸦社鼓。(佛狸是北魏拓跋焘的小名,曾占据南宋北方大半江山,佛狸祠就是拓跋焘的行宫)可是后来的老百姓们却把佛狸当作一位神祇来奉祀,并没有去审查这个神的来历,因为他们都忘了历史。

那时候的老百姓可能买不起书,上不起学,但是现在的我们就不一样了,我们和宋朝人民一样都有过一段屈辱的历史,所以作为程序猿的我要通过备忘录模式写一段程序,记录下这段历史,小鬼子亡我之心依旧不死,我辈要提防,不能让现在的靖国神厕变成下一个佛狸祠。

2.2 解构历史

历史的进程有一定的脉络,作为程序猿的我们可以把历史拆分为这么几个部分:

  1. 历史中的亲历者(个人或群体)
  2. 历史中发生的事情的来龙去脉
  3. 记录历史的人

这三部分数据也是备忘录模式中的三要素:事件的主体、事件的内容、事件的记录者。在事件主体上发生的事情就是事件的内容,事件的内容通过事件记录者进行备份。很显然,在备忘录模式中将事件的主体和事件的内容进行解耦合更有利于程序的扩展和维护。根据上面的描述,我们就可以把这三者之间的对应关系通过UML类图描述出来了:

image-20220922005759719

血泪史

我国的近代史就是一部血泪史,小日本居然都敢下克上,妄图吞并我中华。对于历史上发生的每一个事件,我们需要将其清楚地记录下来,所以在备忘录模式中需要提供一个历史类,这个历史类并不需要记录所有的历史事件,而是用于记录某一个历史事件,原因很简单,这些事件是一个个发生的,所以就可以发生一件记录一件。关于历史类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 历史备忘录类
class History
{
public:
History(string msg) : m_msg(msg) {}
string getHistory()
{
return m_msg;
}
private:
string m_msg;
};

这个类很简单,在创建历史对象的时候,通过构造函数将具体的历史事件封装起来,保存到当前对象中,通过getHistory()可以得到关于具体的历史事件的描述。

脚盆鸡

在备忘录模式中,将小日本和它们做的事情分开了,小日本的虚伪之处就在于只要犯了错就鞠躬道歉,但就是不改并且不承担责任,就这德行居然对曾经杀死的中国人连个鞠躬都没有。所以在这个鬼子类中,需要将它们做的事情封装成一个历史类对象,并且还需要提供一个还原历史的函数,让它们无法否认历史,这个类的定义如下:

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
// 鬼子
class JiaoPenJi
{
public:
void setState(string msg)
{
m_msg = msg;
}

string getState()
{
return m_msg;
}

void beiDaddyDa()
{
cout << "脚盆鸡被兔子狠狠地揍了又揍, 终于承认了它们之前不承认的这些罪行: " << endl;
}

History* saveHistory()
{
return new History(m_msg);
}
void getStateFromHistory(History* history)
{
m_msg = history->getHistory();
}
private:
string m_msg;
};

再介绍一下这个类中提供的几个成员函数:

  • setState(string msg):代表鬼子在我国犯下了某一个罪行,参数对应的就是相关的描述
  • getState():得到鬼子犯下的罪行的相关信息
  • History* saveHistory():将鬼子的罪行封装成一个历史对象,并通过返回值传出
  • getStateFromHistory(History* history):从一个历史对象中,读出相关的历史信息

我们并没有在这个类中将相关的历史事件记录下来,这种事儿鬼子肯定也是不会干的,所以再次进行解耦,把记录历史的任务交给了正义的了解历史的记录者。

记录者

对于发生的历史事件,需要记录者将其一条条保存下来,使其得以保存和被后人所知。关于这个记录者我们可以将其理解为一部被拟人化的史书,我们可以往里边添加历史信息也可以从中获取历史信息,所以这个类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 记录者
class Recorder
{
public:
void addHistory(int index, History* history)
{
m_history.insert(make_pair(index, history));
}
History* getHistory(int index)
{
if (m_history.find(index) != m_history.end())
{
return m_history[index];
}
return nullptr;
}
private:
map<int, History*> m_history;
};

在这个类中,将所有的历史信息通过map 容器存储了起来

  • 通过addHistory 函数 添加历史信息,并保存
  • 通过getHistory 函数 从备份信息中得到想要的历史信息

2.3 罪证

纵观中国历史,古人告诉我们对付倭国的这帮畜生只有一个办法就是使劲揍,打的越疼,它们越顺从。最后来看一下,倭国被收拾之后承认的对我国犯下的累累罪行:

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
int main()
{
vector<string> msg{
"不承认偷了中国的中医!!!",
"不承认发动了侵华战争!!!",
"不承认南京大屠杀!!!!",
"不承认琉球群岛和钓鱼岛是中国的领土!!!",
"不承认731部队的细菌和人体试验!!!",
"不承认对我国妇女做出畜生行为!!!",
"不承认从中国掠夺的数以亿计的资源和数以万计的文物!!!",
"我干的龌龊事儿罄竹难书, 就是不承认......"
};
JiaoPenJi* jiaopen = new JiaoPenJi;
Recorder* recorder = new Recorder;
// 把小日本的罪行记录下来
for (int i = 0; i < msg.size(); ++i)
{
jiaopen->setState(msg.at(i));
recorder->addHistory(i, jiaopen->saveHistory());
}
jiaopen->beiDaddyDa();
for (int i = 0; i < msg.size(); ++i)
{
jiaopen->getStateFromHistory(recorder->getHistory(i));
cout << " -- " << jiaopen->getState() << endl;
}
return 0;
}

输出的结果如下:

1
2
3
4
5
6
7
8
9
脚盆鸡被兔子狠狠地揍了又揍, 终于承认了它们之前不承认的这些罪行:
-- 不承认偷了中国的中医!!!
-- 不承认发动了侵华战争!!!
-- 不承认南京大屠杀!!!!
-- 不承认琉球群岛和钓鱼岛是中国的领土!!!
-- 不承认731部队的细菌和人体试验!!!
-- 不承认对我国妇女做出畜生行为!!!
-- 不承认从中国掠夺的数以亿计的资源和数以万计的文物!!!
-- 我干的龌龊事儿罄竹难书, 就是不承认......

吾辈当自强,牢记历史,振兴中华,锤死小人本!!!当然这要从写好程序开始,最后总结一下备忘录模式的应用场景:

  • 当你需要创建对象状态快照来恢复其之前的状态时(比如游戏存档、文本编辑器)
  • 当直接访问对象的成员变量、获取器或设置器将导致封装被突破时(主要是为了保护数据的安全性,不允许篡改)