"我不能预见每个人的未来,我只能预见我自己的,而且只能预见两分钟"——尼古拉斯.凯奇《惊魂下一秒》2007
无论人写字,画画一样,我们常常有笔误不可避免,
回到过去的某个修改点,做出不同的修改,并继续,
在程序设计的概念里,这常常指版本管理,版本管理保存了每一次(所有)修改的历史,不同时间线,还有合并
图1:版本管理而编辑器的,undo/redo, 则有几点简化,
- 只保留一部分修改记录——通常我们只关心近期的修改,
- undo/redo 为逆操作,但undo不销毁历史,而任何undo之后的修改,则会销毁redo序列
这像是《惊魂下一秒》里的故事,修正有限历史,并让下一秒冲刷掉未来。
undo/redo模式,即为,维护一定长度的修改点队列,并在所有历史修改点里,进行版本切换.
以下我实现了一个简单的undo/redo,版本管理,
//版本数据库,为了实现备份,data须可clone
public class DataBackup<Data> where Data : class, ICloneable, new()
{
//版本队列
List<Data> mDataBackup = new List<Data>() { new Data() };
const int MAX_LEN = 15;//版本历史限制
int dataIdx = 0;//版本号
public Data data
{
get
{
return mDataBackup[dataIdx];
}
}
private void trim()
{
mDataBackup.RemoveRange(dataIdx + 1, mDataBackup.Count - dataIdx - 1);
}
//备份
public void backup()
{
trim();//消除所有未来版本
mDataBackup.Add(data.Clone() as Data);
if (dataIdx > MAX_LEN)
{
mDataBackup.RemoveAt(0);
}
dataIdx = mDataBackup.Count - 1;
}
//撤销
public void undo()
{
dataIdx--;
if (dataIdx < 0)
dataIdx = 0;
}
//重做
public void redo()
{
dataIdx++;
if (dataIdx >= mDataBackup.Count)
dataIdx = mDataBackup.Count - 1;
}
}
图三. 撤销/重做测试
可以看到,这样简单的undo/redo已经足够工作。
实现更精巧的 redo/undo功能,你需要考虑以下问题:
-
当data特别大,每一个版本仅需要保存版本增量,因此需要实现,
gain(data_v1, diff) == data_v2
revert(data_v2, diff) == data_v1 -
MVC模式下,不一定是对于modeller的数据集, 每次备份能可以是controller 操作集 action list,因此control需要实现 一组可逆接口,例如,
addEntity/removeEntiy
changeDeltaPos(x, y) / changeDeltaPos(-x, -y)
而现实中,对于有些编辑器的实现来说,效率并不是一个严重的问题,简单则是更为重要的。
网友评论