撤消重做我们业务中非常常用的功能,我这里写个带限制步数的撤销操作功能的基础实现。

先来了解下撤销重做操作的基本思路。

  1. 撤销的操作往往是先撤回最近的操作,重做则是重做最近一次的撤销操作。所以,撤销重做功能都是符合先进后出的逻辑。所以我们在选择的时候,可以直接采用栈来做实现。
  2. 为了避免撤销操作和重做操作的不一致性,我们需要定义一个对象,能够同时用于撤销和重做的具体操作行为,这样我们在做撤销和重做的操作时,就不至于会出现对不上的情况。
  3. 如果要实现撤销步数限制,那么我们就需要在撤销步数达到最大时,删除最前面多余的撤销步数。

撤销重做栈的实现

因为业务需要,我们这个栈需要提供删除超出的撤销操作,具体方法如下:


    /// <summary>
    /// 包含栈对象的节点
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class LinkedStackNode<T>
    {
        public LinkedStackNode(T value)
        {
            Value = value;
        }

        /// <summary>
        /// 下一个节点对象
        /// </summary>
        public LinkedStackNode<T> Next { get; internal set; }

        /// <summary>
        /// 上一个节点对象
        /// </summary>
        public LinkedStackNode<T> Pre { get; internal set; }

        /// <summary>
        /// 包含对象的值
        /// </summary>
        public T Value { get; }
    }

    /// <summary>
    /// 链表实现的栈,非线程安全
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class LinkedStack<T>
    {
        public LinkedStack(int capacity)
        {
            _capacity = capacity;
        }

        /// <summary>
        /// 根节点
        /// </summary>
        private LinkedStackNode<T> _root;

        /// <summary>
        /// 叶子节点
        /// </summary>
        private LinkedStackNode<T> _leaf;

        /// <summary>
        /// 最大容量
        /// </summary>
        private int _capacity;

        /// <summary>
        /// 获取或设置<see cref="LinkedStack{T}"/>允许容纳的最大数量
        /// </summary>
        public int Capacity
        {
            get
            {
                return _capacity;
            }
            set
            {
                _capacity = value;
                if (_capacity < Count)
                {
                    RemoveLastOf(Count - _capacity);
                }
            }
        }

        /// <summary>
        /// <see cref="LinkedStack{T}"/>所包含<see cref="LinkedStackNode{T}"/>数量
        /// </summary>
        public int Count { get; private set; }

        /// <summary>
        /// 移除并返回顶端的<see cref="LinkedStackNode{T}"/>对象
        /// </summary>
        /// <returns></returns>
        public T Pop()
        {
            if (_leaf == null)
                throw new Exception("当前栈为空");

            var value = _leaf.Value;
            var pre = _leaf.Pre;
            if (pre != null)
            {
                pre.Next = null;
            }

            _leaf = pre;

            Count--;

            return value;
        }

        /// <summary>
        /// 移除并返回顶端的<see cref="LinkedStackNode{T}"/>对象
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public bool TryPop(out T t)
        {
            if (_leaf == null)
            {
                t = default;
                return false;
            }
            t = Pop();
            return true;
        }

        /// <summary>
        /// 在栈顶端插入一个隶属于<see cref="LinkedStackNode{T}"/>的对象
        /// </summary>
        /// <param name="t"></param>
        public void Push(T t)
        {
            var node = new LinkedStackNode<T>(t);

            if (_root == null)
            {
                _root = node;
                _leaf = node;
            }
            else
            {
                if (_root.Next == null)
                {
                    _root.Next = node;
                    node.Pre = _root;
                    _leaf = node;
                }
                else
                {
                    _leaf.Next = node;
                    node.Pre = _leaf;
                    _leaf = node;
                }
            }

            if (Capacity - Count > 1)
            {
                Count++;
                return;
            }
            else
            {
                RemoveLastOf(1);
            }
        }

        /// <summary>
        /// 返回顶端的<see cref="LinkedStackNode{T}"/>对象并不进行移除
        /// </summary>
        /// <returns></returns>
        public T Peek()
        {
            if (_leaf == null)
                throw new Exception("当前栈为空");

            return _leaf.Value;
        }

        /// <summary>
        /// 返回顶端的<see cref="LinkedStackNode{T}"/>对象并不进行移除
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public bool TryPeek(out T t)
        {
            if (_leaf == null)
            {
                t = default;
                return false;
            }
            t = Peek();
            return true;
        }

        /// <summary>
        /// 从栈底端移除节点
        /// </summary>
        /// <param name="count">需要移除的对象数量</param>
        /// <returns></returns>
        int RemoveLastOf(int count)
        {
            var c = Math.Min(count, Count);
            var current = _root;
            for (int i = 0; i < c; i++)
            {
                current = current.Next;
            }

            current.Pre = null;
            _root = current;
            Count -= c;
            return c;
        }
    }

定义可撤销操作接口IUndoable

    /// <summary>
    /// 表示可撤销的
    /// </summary>
    public interface IUndoable
    {
        /// <summary>
        /// 恢复
        /// </summary>
        void Redo();

        /// <summary>
        /// 撤销
        /// </summary>
        void Undo();
    }

定义撤销重做操作

    internal class UndoableOperator
    {
        internal UndoableOperator()
        {
            _redoCommandStack = new LinkedStack<IUndoable>(_defaultCapacity);
            _undoCommandStack = new LinkedStack<IUndoable>(_defaultCapacity);
        }

        readonly LinkedStack<IUndoable> _redoCommandStack;
        readonly LinkedStack<IUndoable> _undoCommandStack;

        /// <summary>
        /// 默认支持撤销重做的步数
        /// </summary>
        const int _defaultCapacity = 100;

        private int capacity = _defaultCapacity;

        public int Capacity
        {
            get
            {
                return capacity;
            }
            set
            {
                capacity = value;

                _redoCommandStack.Capacity = value;
                _undoCommandStack.Capacity = value;
            }
        }

        public bool CanUndo
        {
            get
            {
                return _undoCommandStack.Count > 0;
            }
        }

        public bool CanRedo
        {
            get
            {
                return _redoCommandStack.Count > 0;
            }
        }

        /// <summary>
        /// 插入一个新的操作
        /// </summary>
        /// <param name="undoable"></param>
        public void Insert(IUndoable undoable)
        {
            _undoCommandStack.Push(undoable);
        }

        /// <summary>
        /// 执行一个可撤销操作
        /// </summary>
        internal void Undo()
        {
            if (_undoCommandStack.TryPop(out IUndoable cmd))
            {
                cmd.Undo();
                _redoCommandStack.Push(cmd);
            }
        }

        /// <summary>
        /// 执行一个可恢复操作
        /// </summary>
        internal void Redo()
        {
            if (_redoCommandStack.TryPop(out IUndoable cmd))
            {
                cmd.Redo();
                _undoCommandStack.Push(cmd);
            }
        }
    }

这里没有使用框架自带的栈,真正业务上可以使用System.Collections.Concurrent.ConcurrentStack,线程安全栈来代替以上链表栈。

到这里,撤销重做的功能就基本实现了,只需要在实际业务上稍微做些封装就可以工作了。


本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/%E5%B8%A6%E6%92%A4%E9%94%80%E6%AD%A5%E6%95%B0%E9%99%90%E5%88%B6%E7%9A%84%E6%92%A4%E6%B6%88%E9%87%8D%E5%81%9A%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系