zoukankan      html  css  js  c++  java
  • 用命令模式实现撤销与恢复

    命令模式实现撤销与恢复

    命令模式定义

    将请求封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。
    命令对象可以把行动及参数封装起来,于是这些行动可以被:

    • 重复多次
    • 取消
    • 恢复(取消后又再)

    整个模式的类图如下:

    命令模式

    通过 ICommand 接口,实现了控制类与调用者的解耦。


    下面通过一个简单的实例来详细说明这种解耦以恢复撤销是如何实现。

    假定有一个风扇,当前有四个按钮,分别是 高速模式 , 低速模式 , 撤销恢复

    风扇类类如下(对应类图中的具体类 ConcreteClass):

    有高速运转、低速运转等方法

    public class CeilingFan
    {
         public const int HIGH = 2;
         public const int LOW = 1;
         public const int OFF = 0;
         int speed;
    
         public CeilingFan() { speed = OFF; }
         public void High() { speed = HIGH; }
         public void Low() { speed = LOW; }
         public int getSpeed() { return speed; }
    }
    

    命令接口

    public interface ICommand
    {
        void execute();
        void undo();
    }
    
    

    风扇命令类 (Concrete)

    // 高速运行类
    public class CeilingFanHighCommand : ICommand
    {
        CeilingFan ceilingFan; // 类中不用 new 方法创建类,降低耦合
        int preSpeed;  // 记录执行按键前的状态,便于回测
        public CeilingFanHighCommand(CeilingFan cf)
        {
            ceilingFan = cf;
        }
    
        public void execute()
        {
            preSpeed = ceilingFan.getSpeed();
            ceilingFan.High(); 
        }
    
        public void undo()
        {
            switch(preSpeed)
            {
                case CeilingFan.HIGH:
                    ceilingFan.High();
                    break;
                case CeilingFan.LOW:
                    ceilingFan.Low();
                    break;
                default:
                    ceilingFan.Off();
                    break;
            }
        }
    }
    
    // 低速运行类
    public class CeilingFanLowCommand : ICommand
    {
        CeilingFan ceilingFan; // 类中不用 new 方法创建类,降低耦合
        int preSpeed;  // 记录执行按键前的状态,便于回测
        public CeilingFanHighCommand(CeilingFan cf)
        {
            ceilingFan = cf;
        }
    
        public void execute()
        {
            preSpeed = ceilingFan.getSpeed();
            ceilingFan.Low(); 
        }
    
        public void undo()
        {
            switch(preSpeed)
            {
                case CeilingFan.HIGH:
                    ceilingFan.High();
                    break;
                case CeilingFan.LOW:
                    ceilingFan.Low();
                    break;
                default:
                    ceilingFan.Off();
                    break;
            }
        }
    }
    
    // 关闭类
    public class CeilingFanLowCommand : ICommand
    {
        CeilingFan ceilingFan; 
        int preSpeed;
        public CeilingFanHighCommand(CeilingFan cf)
        {
            ceilingFan = cf;
        }
    
        public void execute()
        {
            preSpeed = ceilingFan.getSpeed();
            ceilingFan.Off(); 
        }
    
        public void undo()
        {
            switch(preSpeed)
            {
                case CeilingFan.HIGH:
                    ceilingFan.High();
                    break;
                case CeilingFan.LOW:
                    ceilingFan.Low();
                    break;
                default:
                    ceilingFan.Off();
                    break;
            }
        }
    }
    

    以上风扇的相关命令构建后,需要被一个类来调用控制它,这个控制类不仅仅可以控制风扇,同时可以控制电灯,冰箱等等,所以不能与风扇耦合。

    控制类 (对应类图中 Control)

    public class Control
    {
        List<ICommand> onCommands;
        Stack<ICommand> undoCommands;
        Stack<ICommand> redoCommands;  // 记录前一个命令, 便于 undo
    
        public Control()
        {
            onCommands = new List<ICommand>();
            undoCommands = new Stack<ICommand>();
            redoCommands = new Stack<ICommand>();
        }
    
        public void SetCommand(int slot, ICommand onCmd)
        {
            onCommands[slot] = onCmd;
        }
    
        public void OnButtonWasPressed(int slot)
        {
            if (onCommands[slot] != null)
            {
                onCommands[slot].execute();
                undoCommands.Push(onCommands[slot]);
            }
        }
    
        public void UndoButtonWasPressed() // 撤销,此处用 stack 后进先出的特性
        {
            if (undoCommands.Count > 0)
            {
                ICommand cmd = undoCommands.Pop();
                redoCommands.Push(cmd);
                cmd.undo();
            }
        }
    
        public void RedoButtonWasPressed()
        {
            if(redoCommands.Count > 0)
            {
                ICommand cmd = redoCommands.Pop();
                undoCommands.Push(cmd);
                cmd.execute();
            }
        }
    }
    

    以上一个命令模式大体上完成了。
    下面让客户进行调用测试

    // 测试类 (类途中的 RemoteLoader)
    class Program
    {
        static void Main(string[] args)
        {
            CeilingFan cf = new CeilingFan();
            CeilingFanHighCommand cfh = new CeilingFanHighCommand(cf);
            CeilingFanLowCommand cfl = new CeilingFanLowCommand(cf);
    
            Control ctr = new Control();
            // 假设 button0, button1 分别为高速低速
            ctr.SetCommand(0, cfh);
            ctr.SetCommand(1, cfl);
    
            ctr.OnButtonWasPressed(0);
            ctr.OnButtonWasPressed(1);
            ctr.UndoButtonWasPressed();
            ctr.RedoButtonWasPressed();
        }
    }
    

    输出内容如下:

    turn ceilfan to high speed!

    turn ceifan to low speed!

    turn ceilfan to high speed!

    turn ceifan to low speed!

    撤销与重做功能就此实现。整个过程中,最关键部分是命令对象的封装以及控制类与具体工厂类耦合的解除。

  • 相关阅读:
    在IE和Firfox获取keycode
    using global variable in android extends application
    using Broadcast Receivers to listen outgoing call in android note
    help me!virtual keyboard issue
    using iscroll.js and iscroll jquery plugin in android webview to scroll div and ajax load data.
    javascript:jquery.history.js使用方法
    【CSS核心概念】弹性盒子布局
    【Canvas学习笔记】基础篇(二)
    【JS核心概念】数据类型以及判断方法
    【问题记录】ElementUI上传组件使用beforeupload钩子校验失败时的问题处理
  • 原文地址:https://www.cnblogs.com/yaolin1228/p/10588555.html
Copyright © 2011-2022 走看看