• 设计模式——命令模式


    一、命令模式

    1.1 概述

    在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。

    在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

    再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。

    1.2 定义

    将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

    1.3 结构

    • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
    • 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
    • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
    • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

    在这里插入图片描述

    1.4 例子

    顾客下单给服务员,服务员告知厨师
    在这里插入图片描述

    public interface Command {
        void execute();//只需要定义一个统一的执行方法
    }
    
    public class OrderCommand implements Command {
    
        //持有接受者对象
        private SeniorChef receiver;
        private Order order;
    
        public OrderCommand(SeniorChef receiver, Order order){
            this.receiver = receiver;
            this.order = order;
        }
    
        public void execute()  {
            System.out.println(order.getDiningTable() + "桌的订单:");
            Set<String> keys = order.getFoodDic().keySet();
            for (String key : keys) {
                receiver.makeFood(order.getFoodDic().get(key),key);
            }
    
            try {
                Thread.sleep(100);//停顿一下 模拟做饭的过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
            System.out.println(order.getDiningTable() + "桌的饭弄好了");
        }
    }
    
    public class Order {
        // 餐桌号码
        private int diningTable;
    
        // 用来存储餐名并记录份数
        private Map<String, Integer> foodDic = new HashMap<String, Integer>();
    
        public int getDiningTable() {
            return diningTable;
        }
    
        public void setDiningTable(int diningTable) {
            this.diningTable = diningTable;
        }
    
        public Map<String, Integer> getFoodDic() {
            return foodDic;
        }
    
        public void setFoodDic(String name, int num) {
            foodDic.put(name,num);
        }
    }
    
    // 资深大厨类 是命令的Receiver
    public class SeniorChef {
    
        public void makeFood(int num,String foodName) {
            System.out.println(num + "份" + foodName);
        }
    }
    
    public class Waitor {
    
        private ArrayList<Command> commands;//可以持有很多的命令对象
    
        public Waitor() {
            commands = new ArrayList();
        }
        
        public void setCommand(Command cmd){
            commands.add(cmd);
        }
    
        // 发出命令 喊 订单来了,厨师开始执行
        public void orderUp() {
            System.out.println("美女服务员:叮咚,大厨,新订单来了.......");
            for (int i = 0; i < commands.size(); i++) {
                Command cmd = commands.get(i);
                if (cmd != null) {
                    cmd.execute();
                }
            }
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            //创建2个order
            Order order1 = new Order();
            order1.setDiningTable(1);
            order1.getFoodDic().put("西红柿鸡蛋面",1);
            order1.getFoodDic().put("小杯可乐",2);
    
            Order order2 = new Order();
            order2.setDiningTable(3);
            order2.getFoodDic().put("尖椒肉丝盖饭",1);
            order2.getFoodDic().put("小杯雪碧",1);
    
            //创建接收者
            SeniorChef receiver=new SeniorChef();
            //将订单和接收者封装成命令对象
            OrderCommand cmd1 = new OrderCommand(receiver, order1);
            OrderCommand cmd2 = new OrderCommand(receiver, order2);
            //创建调用者 waitor
            Waitor invoker = new Waitor();
            invoker.setCommand(cmd1);
            invoker.setCommand(cmd2);
    
            //将订单带到柜台 并向厨师喊 订单来了
            invoker.orderUp();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    1.5 优缺点

    优点

    • 通过引入中间件(抽象接口)降低系统的耦合度。
    • 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
    • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
    • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
    • 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

    缺点

    • 使用命令模式可能会导致某些系统有过多的具体命令类。
    • 系统结构更加复杂。

    1.6 使用场景

    当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。

    命令模式通常适用于以下场景。

    • 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
    • 系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
    • 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
    • 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现

    1.7 jdk源码解析

    Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

    //命令接口(抽象命令角色)
    public interface Runnable {
    	public abstract void run();
    }
    
    //调用者
    public class Thread implements Runnable {
        private Runnable target;
        
        public synchronized void start() {
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
    
            group.add(this);
    
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                }
            }
        }
        
        private native void start0();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。

    /**
     * jdk Runnable 命令模式
     *		TurnOffThread : 属于具体
     */
    public class TurnOffThread implements Runnable{
         private Receiver receiver;
        
         public TurnOffThread(Receiver receiver) {
         	this.receiver = receiver;
         }
         public void run() {
         	receiver.turnOFF();
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    /**
     * 测试类
     */
    public class Demo {
         public static void main(String[] args) {
             Receiver receiver = new Receiver();
             TurnOffThread turnOffThread = new TurnOffThread(receiver);
             Thread thread = new Thread(turnOffThread);
             thread.start();
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.8 命令模式的扩展

    在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令,其具体结构图如图所示。
    在这里插入图片描述

    package command;
    import java.util.ArrayList;
    public class CompositeCommandPattern {
        public static void main(String[] args) {
            AbstractCommand cmd1 = new ConcreteCommand1();
            AbstractCommand cmd2 = new ConcreteCommand2();
            CompositeInvoker ir = new CompositeInvoker();
            ir.add(cmd1);
            ir.add(cmd2);
            System.out.println("客户访问调用者的execute()方法...");
            ir.execute();
        }
    }
    //抽象命令
    interface AbstractCommand {
        public abstract void execute();
    }
    //树叶构件: 具体命令1
    class ConcreteCommand1 implements AbstractCommand {
        private CompositeReceiver receiver;
        ConcreteCommand1() {
            receiver = new CompositeReceiver();
        }
        public void execute() {
            receiver.action1();
        }
    }
    //树叶构件: 具体命令2
    class ConcreteCommand2 implements AbstractCommand {
        private CompositeReceiver receiver;
        ConcreteCommand2() {
            receiver = new CompositeReceiver();
        }
        public void execute() {
            receiver.action2();
        }
    }
    //树枝构件: 调用者
    class CompositeInvoker implements AbstractCommand {
        private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>();
        public void add(AbstractCommand c) {
            children.add(c);
        }
        public void remove(AbstractCommand c) {
            children.remove(c);
        }
        public AbstractCommand getChild(int i) {
            return children.get(i);
        }
        public void execute() {
            for (Object obj : children) {
                ((AbstractCommand) obj).execute();
            }
        }
    }
    //接收者
    class CompositeReceiver {
        public void action1() {
            System.out.println("接收者的action1()方法被调用...");
        }
        public void action2() {
            System.out.println("接收者的action2()方法被调用...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
  • 相关阅读:
    【面试普通人VS高手系列】CPU飙高系统反应慢怎么排查?
    【js】-【贪心算法】-笔记
    HTML CSS
    【vue】axios请求封装,二次封装
    设计模式-工厂方法模式(C++)
    【前端实例教程】使用 HTML CSS 和 JavaScript 创建自定义搜索+下拉选择框菜单
    base64转为file
    C++ 基础入门 之 数组/一维数组/二维数组/定义形式/数组名的作用
    04.8. 数值稳定性和模型初始化
    CSS详细解析二
  • 原文地址:https://blog.csdn.net/qq_51114283/article/details/125907558