【设计模式】怒敲两百多行代码来解释命令模式!

什么是命令模式(Command)

概念

命令模式(Command Pattern)属于行为型模式,定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。命令模式讲究的是解耦合,可延迟,像线程就是命令模式的典例,如果不适用命令模式的话,那么就无法将我们要执行的方法交给线程去执行,必须立马执行方法。

举个例子,我们去餐厅吃饭,服务员都会拿个菜单给我们,这个菜单就是已经写好的“命令”,只需要我们跟服务员(请求者)说想要吃什么菜(命令),那服务员就会通知厨师(实现者)去做什么菜,我们觉得菜够了,也可以取消没有做的菜;还有电视机的遥控器,当我们想换台时,就拿起遥控器(请求者),按下其他台的按钮(命令),电视机(实现者)就会换台了,我们想退回去看直接点返回即可。

其实我也一直在想,这两个例子中“我们”是处于一个什么样的角色,“我们”是处于命令模式这个模块之外的角色,我们想实现需求就得去调用请求者,像点菜,你直接跟厨师说,厨师哪里会理你,还有你直接对电视机喊也没用。这里我们也可以体会到请求者是对命令如何使用做了封装,我们想使用只需要和请求者交互即可。

命令模式也非常像我们打开Windows或Linux的命令操作界面,我们只需要输入命令和参数,就可以执行功能了。

【设计模式】怒敲两百多行代码来解释命令模式!

优点

  1. 降低系统的耦合度。通过引入命令这类对象,降低了请求者和实现者的耦合。
  2. 符合开放封闭原则。想要新增操作,只需要写新的命令类即可。
  3. 可以延迟执行、撤销,排队请求。

大部分的设计模式都是为了解耦合,高内聚,我写的都有点腻了。

缺点

  1. 增加了系统的复杂性。使用命令模式可能会使系统产生大量的命令类,每个操作都得去实现一个相应的命令类,大量的命令类会增加系统的复杂性。
  2. 降低了代码的可读性。使用命令模式是通过命令的方式把请求者和实现者分离开来,这不仅带来了代码设计上的困难,也增加了系统的理解难度。

大部分的设计模式都是存在一定的实现难度和增加系统的复杂性。

原则

“+”代表遵守,“-”代表不遵守或者不相关

原则 开放封闭 单一职责 迪米特 里氏替换 依赖倒置 接口隔离 合成复用
+ + - - + - -

适用场景

  1. 基于事件驱动的模块。像浏览器的按钮、操作系统的控制台,都是一个动作或者一行命令触发就要去完成相应的操作。
  2. 要求请求者和实现者解耦。请求者和实现者不直接进行交互。
  3. 存在可以延迟执行、撤销、回滚等情况。像记录日志、保存信息到数据库这些。

如何实现

想要实现命令模式,需要以下四样东西:

  1. 抽象命令接口:用来声明命令,拥有执行命令的抽象方法 execute()。
  2. 具体命令类:实现抽象命令接口,并调用实现者完成功能。
  3. 实现者/接收者类:为具体命令类提供服务。
  4. 请求者/调用者类:命令的调用者,负责管理和执行命令。

上类图

这里我复制其他网站看到的命令模式的类图。大家可以看看了解一下,然后再去看我写的代码。

【设计模式】怒敲两百多行代码来解释命令模式!

上例子

夏天来了,家家户户都装了空调,现有一家安装空调以及维修空调的公司,这公司的领导负责给工人安排安装装和维修空调工作,工人接收到工作后就去安装和维修。

【设计模式】怒敲两百多行代码来解释命令模式!

类图:

【设计模式】怒敲两百多行代码来解释命令模式!

代码:

/**
 * Created on 2021/6/4.
 *
 * @author xuxiaobai
 */
public class CommandTest {
    public static void main(String[] args) {
        //领导
        Leader leader=new Leader();
        //只有张三一个工人
        Worker worker = new Worker("张三");
        System.out.println("领导安排工作");
        leader.addPlan(new InstallWork("A镇58号", worker));
        leader.addPlan(new RepairWork("B镇23号", worker));
        //哎,领导取消安排了
        leader.cancel();
        //重新安排
        leader.addPlan(new InstallWork("C镇15号", worker));
        leader.addPlan(new InstallWork("D镇8号", worker));
        leader.addPlan(new InstallWork("E镇6号", worker));

        System.out.println("领导下达命令");
        leader.order();
        /**
         * 结果:
         * 领导安排工作
         * 取消工作安排
         * 领导下达命令
         * 张三骑上了心爱的面包车,到达了C镇15号
         * 张三开始工作了
         * 卸下空调
         * 装内机
         * 装外机
         * 检验
         * 完成收工
         * 张三骑上了心爱的面包车,到达了D镇8号
         * 张三开始工作了
         * 卸下空调
         * 装内机
         * 装外机
         * 检验
         * 完成收工
         * 张三骑上了心爱的面包车,到达了E镇6号
         * 张三开始工作了
         * 卸下空调
         * 装内机
         * 装外机
         * 检验
         * 完成收工
         */
    }
}


/**
 * 实现者/接收者类
 * 工人类
 * 功能:到达目的地、装空调、修空调
 */
class Worker{

    private String name;
    //是否携带空调
    private boolean airConditioner=false;

    public Worker(String name){
        this.name=name;
    }

    /**
     * 到达目的地
     * @param address
     */
    public void arrive(String address){
        System.out.print(name+"骑上了心爱的面包车,到达了");
        System.out.println(address);
    }

    /**
     * 带上空调
     */
    public void getAirConditioner(){
        this.airConditioner=true;
    }

    /**
     * 放下空调
     */
    private void putAirConditioner(){
        this.airConditioner=false;
    }

    /**
     * 装空调
     */
    public void installAirConditioner(){
        System.out.println(name+"开始工作了");
        System.out.println("卸下空调");
        putAirConditioner();
        System.out.println("装内机");
        System.out.println("装外机");
        System.out.println("检验");
        System.out.println("完成收工");
    }


    /**
     * 修空调
     */
    public void repairAirConditioner(){
        System.out.println(name+"开始工作了");
        System.out.println("拆外机壳");
        System.out.println("检查");
        System.out.println("拆内机壳");
        System.out.println("检查");
        System.out.println("维修");
        System.out.println("完成收工");
    }


}

/**
 * 抽象命令接口
 * 这里只能用接口,所以就用接口了,如果你想写个例子可以考虑写个抽象类
 * 工作类
 */
interface Work{
    void execute();
}

/**
 * 具体命令类
 * 装空调
 */
class InstallWork implements Work{
    //地址
    private String address;
    //工人
    private Worker worker;
    //领空调凭证
    private boolean voucher=false;

    public InstallWork(String address,Worker worker){
        this.address=address;
        this.worker=worker;
        this.voucher=true;
    }


    @Override
    public void execute() {
        if (voucher){
            //有凭证才能取空调
            worker.getAirConditioner();
        }else {
            System.out.println("没有取空调凭证,拿不到空调");
            return;
        }
        worker.arrive(address);
        worker.installAirConditioner();
    }
}

/**
 * 具体命令类
 * 修空调
 */
class RepairWork implements Work{
    //地址
    private String address;
    //工人
    private Worker worker;

    public RepairWork(String address,Worker worker){
        this.address=address;
        this.worker=worker;
    }


    @Override
    public void execute() {
        worker.arrive(address);
        worker.repairAirConditioner();
    }
}

/**
 * 空调公司领导
 */
class Leader{
    List<Work> works=new ArrayList<>();

    /**
     * 增加工作安排
     * @param work
     */
    public void addPlan(Work work){
        works.add(work);
    }

    /**
     * 下发工作
     */
    public void order(){
        Iterator<Work> iterator = works.iterator();
        while (iterator.hasNext()){
            Work work = iterator.next();
            work.execute();
            iterator.remove();
        }
    }

    /**
     * 取消安排
     */
    public void cancel(){
        System.out.println("取消工作安排");
        works.clear();
    }

}


大家简单看一下,理一下思路就可以了。

如果是不使用命令模式的话,那么就得我们直接操作Worker对象了,什么步骤都要我们来操作,而且不能够延时执行。使用了命令模式,我们甚至可以使用工厂来创建命令类,这样使用起来就更加地简便了。

总结

命令模式的关键在于它把请求/操作封装成一个命令对象,这些命令对象都是提前写好的,然后把这些命令交给实现者去完成操作,在命令没有执行之前可以撤销、新增命令。

我在学习命令模式的时候,就一直在想命令模式跟写方法有什么区别?直接写方法也是封装操作逻辑,但这样的话就不能延迟执行、撤销和回滚。

上一篇:php应用容器workerman_TcpConnection类pauseRecv接口说明及范例


下一篇:Linux中的workqueue机制