2023-03-27
原文作者:ClarenceZero 原文地址:https://blog.csdn.net/ClarenceZero/article/details/106887075

命令模式

定义

命令模式请求封装成对象,以便使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作。

一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面看,其他对象不知道空间哪个接收者进行了哪些动作,只知道如果调用了execute()方法,请求的目的就能达到。

类图

202303272245210271.png

实现

1. 定义Command接口

    /**
     * 命令定义
     */
    public interface Command {
        /**
         * 执行命令
         */
        void execute();
    
        /**
         * 回退命令
         */
        void undo();
    }

2. 定义命令接口实现类

    /**
     * 包装开灯、关灯指令
     * ConcreteCommand
     */
    public class LightOffCommand implements Command {
        Light light;
        public LightOffCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.off();
        }
    
        @Override
        public void undo() {
            light.on();
        }
    }
    /**
     * 包装开灯、关灯指令
     * ConcreteCommand
     */
    public class LightOnCommand implements Command {
        Light light;
        public LightOnCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.on();
        }
    
        @Override
        public void undo() {
            light.off();
        }
    }

3.定义命令接收者

    /**
     * Receiver
     * 具体指令执行者
     */
    public class Light {
    
        public void on() {
            System.out.println("Light ON.");
        }
        public void off() {
            System.out.println("Light OFF.");
        }
    }

4. 定义调用者

    /**
     * 使用命令对象
     * 假设只有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置。
     * Invoker
     */
    public class RemoteControl {
        // 有一个插槽持有命令,而这个命令控制着一个着墨
        private Command[] onCommands;
        private Command[] offCommands;
        private Command undoCommand;
        public RemoteControl() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            Command noCommand = new NoCommand();
            for (int i = 0; i < 7; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
            undoCommand = noCommand;
        }
    
        /**
         * 设置插槽控制的命令。
         */
        public void setCommand(int slot, Command onComand, Command offCommand) {
            onCommands[slot] = onComand;
            offCommands[slot] = offCommand;
        }
    
        /**
         * 当按下按钮时,这个方法就会被调用。使得当前命令衔接插槽,并调用它的`execute()`方法
         */
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
            undoCommand = onCommands[slot];
        }
    
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
            undoCommand = offCommands[slot];
        }
    
        public void undoButtonWasPushed() {
            undoCommand.undo();
            System.out.println("UNDO");
        }
    
        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append("\n-------- Remote Control ---------\n");
            for (int i = 0; i < onCommands.length; i++) {
                if (onCommands[i] == null) {
                    continue;
                }
                sb.append("[slot " + i + "] " + onCommands[i].getClass().getName() + "    " + offCommands[i].getClass().getName() + "\n");
            }
            return sb.toString();
        }
    }

5. 定义Client

    public class M {
        /**
         * M 为命令模式的客户。Client
         */
        public static void main(String[] args) {
            // 遥控器就是调用者,会传入一个命令对象,可以用来发出请求
            RemoteControl remoteControl = new RemoteControl();
            // 现在创建了一个电灯对象,此对象也是请求的接收者
            Light light = new Light();
            // 创建一个命令,并将接收者传递给命令
            LightOnCommand lightOnCommand = new LightOnCommand(light);
            LightOffCommand lightOffCommand = new LightOffCommand(light);
    
            // 模拟按下按钮
            // 在简单的遥控器中,我们先用一个 `打开电灯`命令加载按钮插槽,后来替换为`打开车库电灯`命令。
            // 遥控器插槽并根本不在乎所拥有的是什么命令对象。只要该命令对象实现了`command`接口就可以。
            GarageDoor garageDoor = new GarageDoor();
            GarageDoorOnCommand garageDoorOnCommand = new GarageDoorOnCommand(garageDoor);
            GarageDoorOffCommand garageDoorOffCommand = new GarageDoorOffCommand(garageDoor);
    
            remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
            remoteControl.setCommand(1, garageDoorOnCommand, garageDoorOffCommand);
    
            remoteControl.onButtonWasPushed(0);
            remoteControl.onButtonWasPushed(1);
            remoteControl.offButtonWasPushed(0);
            remoteControl.undoButtonWasPushed();
            remoteControl.offButtonWasPushed(1);
    
        }
    }

带状态回滚

    public class CeilingFanOffCommand implements Command {
        CeilingFan ceilingFan;
        // 增加局部状态变量,以便追踪吊扇之前的速度
        int prevSpeed;
        public CeilingFanOffCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
    
        @Override
        public void execute() {
            // 先将它的状态记录下来,以便需要撤销的时候用到
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.off();
        }
    
        @Override
        public void undo() {
            if (prevSpeed == 3) {
                ceilingFan.high();
            } else if (prevSpeed == 2) {
                ceilingFan.medium();
            } else if (prevSpeed == 1) {
                ceilingFan.low();
            } else {
                ceilingFan.off();
            }
        }
    }

总结

  1. 命令模式可以将动作的请求者动作的执行者对象中解耦。
  • 遥控器应该知道如何解读按钮被按下的动作,然后发出正确的请求。但是不知道这些家电自动化的细节。
  • 使用状态实现撤销。
  • 命令模式更多用途: 队列请求。命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去。就像是一般的对象一样。利用这样的特性衍生出: 日程安排(Scheduler)、线程池、工作队列。
  • 在某一端添加命令,然后另一端则是线程。线程进行如下操作: 从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令。
  1. 每个设计模式都应该由两部分组成: 一是应用场景、二是解决方案。如果只关注代码实现,会产生大部分设计模式看起来很相似的感觉。实际上,设计模式之间的主要区别在于设计意图,即应用场景。

  2. 命令模式VS策略模式

    1. 策略模式包含策略的定义、创建和使用三部分。从代码角度看,非常类似于工厂模式。区别在于,策略模式侧重“策略”或“算法”这个特定的应用场景,用来解决根据运行时状态从一组策略中选择不同策略的问题,而工厂模式侧重封装对象的创建过程,这里的对象没有任何业务场景的限定,可以是策略,但也可以是其他东西。从设计意图上来,这两个模式完全是两回事儿。
    2. 命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。
阅读全文