// // // // // // // // //
///2013.2.19
// // // // // // // // //
"曾经有一份真挚的爱情摆在我面前,
我没有去珍惜,
等到失去了才追悔莫及。
如果上天能再给我一次机会,
我一定要说出那三个字——我要当导演。"
时光如梭。
还记得当年那个踢过少林足球,点过秋香,当过喜剧之王的至尊宝,
如今已俨然变身成了胡茬大叔,
做起了导演。
时光如梭。
当年,现在。
也许你一定还有一件事情,
至今后悔当初没那样做。
很可惜,
我们没有月光宝盒。
很幸运,
因为我们有Memento(模式),
可以让我们重头来过。
当然,
这个模式的具体应用更是众所周知——"Undo"。
中文里我们管它叫做"撤销"。
【核心】返回过去的存在状态。
UML图:
这个存在于众多软件之中的功能,
更多时候我们只是简单地按下Ctrl+Z来执行。
但是,
有问题。
大家想过没有,
撤销,这项功能,
该存在于代码结构的什么位置呢?
存在于撤销所在的本体类?
不行,
因为假设我们将本体进行了修改,
按下撤销时,
本体连同撤销一起返回了过去的状态。
(关于这个问题就好像大炮对准自己发射,将炮本身炸坏了然后仍要发射炮弹一样……请原谅笔者无力的解释^_^|||)
存在与外部类?
也不好,
因为毕竟是自己的状态,
怎么能公开呢?
但如果外部类不公开,
自己又怎么将状态传过去呢?
(关于这个问题就好像要将一枚硬币放进封口的瓶子里一样……请再次原谅笔者无力的解释T_T)
等等,
第二个问题我们是不是可以简化成下面这句话?
想办法访问外部类的私有数据。
对了,
我猜你已经想到了。
在C++中,
解决这个问题的关键就是——
友元类。
示例代码:
【大致思路】
Originator是一个系统,状态使用state(string类型)来储存。
Memento是其友元类,用于存储Originator旧状态。
Memento.h
#ifndef _MEMENTO_H_ #define _MEMENTO_H_ #include<string> class Memento; using namespace std; class Originator { public: Originator(const string& s); ~Originator(); void setState(const string& s); string getState(); void backToLastState(); private: Memento* mem; string state; }; class Memento { public: Memento(){} ~Memento(){} private: friend class Originator; //Can't be access by other classes. void setOldState(const string& s); string getOldState(); string oldState; }; #endif
Memento.cpp
#include"Memento.h" using namespace std; Originator::Originator(const string& s) { state = s; //Create a new memento. this->mem = new Memento(); mem->setOldState(state); } Originator::~Originator() { if(mem != nullptr) delete mem; } void Originator::setState(const string& s) { //Save the old state. mem->setOldState(state); //Refresh the current state. state = s; } string Originator::getState() { //Return the current state. return state; } void Originator::backToLastState() { setState(mem->getOldState()); } void Memento::setOldState(const string& s) { oldState = s; } string Memento::getOldState() { return oldState; }
main.cpp
#include"Memento.h" #include<iostream> using namespace std; int main() { Originator* typer = new Originator("Zero"); cout<<"Original state -> "<<typer->getState()<<endl; //Change state. typer->setState("First"); cout<<"Modified state -> "<<typer->getState()<<endl; //Back to old state. typer->backToLastState(); cout<<"Last state -> "<<typer->getState()<<endl; return 0; }
输出结果:
注意事项:
其实代码本身并没有太多可以讲解的,
毕竟注释已经写得很清楚了。
不过关于撤销还是有点话想说的。
不知道大家有木有注意到过,
撤销也是分为不同类别的。
最常见的是notepad(记事本)的撤销,
如果连续按下去的话,
它只是在当前命令与上一命令之间来回切换而已。
就像这样:A->B->A->B->A->.....
这一类的实现是很容易的,
笔者的撤销就属于这一类。
另一类的撤销就稍微复杂点了,
比如说各个编译器(VS,MonoDeveloper等)的撤销命令,
按下去Ctrl+Z就是一直返回过去的状态,
就像这样:E->D->C->B->A->....
但是相对的,
它也具有"重做"命令(一般是Ctrl+Y),
其与撤销是反操作的,
就像这样:A->B->C->D->E
这一类的撤销的实现稍微复杂点,
涉及到了堆栈的使用。
但每个软件的设计不同,
因此也不能一概而论。
除此之外,
还有一类撤销,
可以随时返回过去任意状态,
最明显的例子就是PS的历史记录了,
点到哪里就返回哪里。
这类撤销的设计方式类似于List或是Hash table.
顺便说一句,
PS具有多种撤销方式,
因为除了历史记录之外它也可以Ctrl+Z。
其实除了PS,
大多数艺术类软件都是同样的设计方式,
例如3ds Max,Mudbox,或是Illustrator,
因为这样非常方便进行艺术创作(想一想为什么)。
说一点题外话吧,
——
其实有时候真想在自己肚子上安装一个撤销按钮,
一按下去,
扑哧,就返回过去了。
#Problems
但这种想法是很超前的,
因为现实世界这个系统太过于庞大,
目前还没有一个处理器能处理万物的行为,
还有没有一个硬盘可以存储万物的状态,
更没有一个强大的系统可以完全模拟这一切。
尽管如此,
有时候笔者也会站在洒满阳光的窗台前,
望着灿烂了整个暮空的夕阳,
思考这种系统的设计方式。
目前最初级的想法是创建一个4纬位数组,
三个轴存储空间位运算,
最后一个存储时间的位运算,
然后每隔一段时间将现有数组与原数组进行对比,
发生改变的部分push到一个状态栈中去,
然后当按下肚子上的撤销按钮时,
就从此状态栈中pop一个状态出来,
将自己现有状态替换掉。
但是实现起来有困难,
困难是什么呢?
goto #Problems;
郭德纲都上春晚了,
笔者这个单纯的想法还没能实现,
不得不说这是人生一大憾事。
所以,
在发明出这种系统(机器?)之前,
还是珍惜当下吧。
不要让自己有任何遗憾,
这样就不会想穿梭时光了。