"我不能預見每個人的未來,我只能預見我自己的,而且只能預見兩分鐘"——尼古拉斯.凱奇《驚魂下一秒》2007
無論人寫字,畫畫一樣,我們常常有筆誤不可避免,
回到過去的某個修改點,做出不同的修改,并繼續,
在程序設計的概念里,這常常指版本管理,版本管理保存了每一次(所有)修改的歷史,不同時間線,還有合并
圖1:版本管理
而編輯器的,undo/redo, 則有幾點簡化,
- 只保留一部分修改記錄——通常我們只關心近期的修改,
- undo/redo 為逆操作,但undo不銷毀歷史,而任何undo之后的修改,則會銷毀redo序列
圖2: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_v1MVC模式下,不一定是對于modeller的數據集, 每次備份能可以是controller 操作集 action list,因此control需要實現 一組可逆接口,例如,
addEntity/removeEntiy
changeDeltaPos(x, y) / changeDeltaPos(-x, -y)
而現實中,對于有些編輯器的實現來說,效率并不是一個嚴重的問題,簡單則是更為重要的。