2023-03-27  阅读(1)
原文作者: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. 命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文