用Command模式簡單的實現Undo&Redo功能


許多GUI程序中提供一個"撤銷&重做"的功能,這個功能對用戶來說非常友好;本文就簡單的介紹一下如何用C#實現該功能。

實現Undo&Redo功能的基本模型是帶撤銷功能的命令模式,它將每步操作保存為一個命令對象,如下所示:

    interface Icommand
    {
        void Do();
        void Undo();
    }

其中Do函數執行功能,Undo函數回滾功能。這樣就把命令給實體化了,只要將命令對象給保存下來,需要撤銷時執行Undo函數,重做時執行Do函數即可。

有了這個基本思路后,下面就是實現細節了:

  1. 申請兩個Stack來保存命令對象:UndoStack和RedoStack
  2. 執行命令時,將命令序列化為Command對象,執行Do方法,存入UndoStack,清空RedoStack
  3. 撤銷命令時,從UndoStack中取出命令,執行Undo方法,存入RedoStack
  4. 重做命令時,從RedoStack中取出命令,執行Do方法,存入UndoStack

一個簡單的實現如下:

    class CommandManager
    {
        Stack<Command> redoStack = new Stack<Command>();
        Stack<Command> undoStack = new Stack<Command>();

        public void AddCommand(Action doCmd, Action undoCmd)
        {
            var cmd = new Command(doCmd, undoCmd);
            cmd.Do();

            undoStack.Push(cmd);
            redoStack.Clear();
        }

        public bool Undo()
        {
            if (undoStack.Count == 0)
                return false;

            var cmd = undoStack.Pop();
            redoStack.Push(cmd);

            cmd.Undo();
            return true;
        }

        public bool Redo()
        {
            if (redoStack.Count == 0)
                return false;

            var cmd = redoStack.Pop();
            undoStack.Push(cmd);

            cmd.Do();
            return true;
        }

        class Command
        {
            public Action Do { get; private set; }
            public Action Undo { get; private set; }

            public Command(Action doCmd, Action undoCmd)
            {
                this.Do = doCmd;
                this.Undo = undoCmd;
            }
        }
    }

用C#實現起來還是非常簡潔的,就幾十行代碼。

遺留問題:命令對象何時釋放

前面的實現雖然非常簡單,但存在一個遺留問題:每一個命令對象都保存在UndoStack中了,這樣隨着程序的執行,UndoStack中記錄的命令越來越多,占用內存得不到釋放。對於這個問題,一般有如下幾種策略:

  1. 不釋放命令對象。一般需要Undo&Redo功能都是些GUI程序,這些程序大多不會持續運行,並且對內存的占用也沒有太大限制,命令對象一般也不會占用多少內存。保存所有命令對象不會對程序造成什么影響。
  2. 命令堆棧維持固定的長度:當命令堆棧的長度超過閾值的時候,刪除最開始壓入的命令。這種策略用得最多,但這樣帶來的問題就是無法實現無限Undo。
  3. 將命令堆棧保存到文件:將命令序列化保存到文件,需要使用時從文件中還原。這種方式可以實現無限Undo,但序列化命令往往是件比較麻煩的事情,反序列化時也要消耗時間。
  4. 綜合2,3兩種方案:內存中保持固定長度的命令對象,超過閾值的保存到文件。這種方式能有效解決反序列化的耗時問題,也能實現無限Undo。但實現起來也最為麻煩。

基於篇幅所限,本文就不進一步討論和實現了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM