设计模式【14】-- 从智能音箱中学习命令模式

设计模式

开局还是那种图,各位客官往下看...

张无忌学太极拳,忘记了所有招式,打倒了"玄冥二老",所谓"心中无招"。设计模式可谓招数,如果先学通了各种模式,又忘掉了所有模式而随心所欲,可谓OO之最高境界。

命令模式是什么?

在面向对象程式设计的范畴中,命令模式(Command Pattern)是一种设计模式,它尝试以物件来代表实际行动。

命令模式是一种行为型模式,它将请求以命令的形式包裹在对象里面,传递给调用对象,调用对象寻找匹配该命令的对象,将命令给该对象执行。也就是分为了三步:

  • 1、命令被包裹在请求对象里,传递给调用对象。
  • 2、调用对象查找匹配该命令(可以处理该命令)的对象,将该命令传递给匹配的对象。
  • 3、该对象执行传递给它的命令。

一般而言,在软件开发中,行为的请求者和行为的执行者是紧密耦合在一起的,调用关系简单易懂,但是这样不容易拓展,有些时候,我们需要记录、撤销、重做处理的时候,不易修改。因此我们需要将命令抽象化,封装起来,不直接调用真正的执行者方法,易于拓展。

举个现实中的例子,比如我们去餐厅吃饭:

点餐(写好菜单,发起请求) --> 订单系统处理,生成订单(创建命令) --> 厨师获取到订单,开始做菜(执行命令),在这个过程中我们并没有直接与厨师交谈,不知道那个厨师做,厨师也不知道是哪个顾客需要,只需要按照订单处理就可以。

又比如,我们经常使用智能音响,我经常叫它 ”小度小度,帮我打开空调“,”小度小度,帮我打开窗帘“等等,在整个过程中,我发出命令 --> 小度接受到命令,包装成为请求 --> 让真正接收命令的对象处理(空调或者窗帘控制器),我没有手动去操作空调和窗帘,小度也可以接受各种各样的命令,只要接入它,我都通过它去操作。

命令模式中的角色

在命令模式里面,一共存在以下的角色:

  • Command(抽象命令):命令有通用的特性,将命令抽象成一个类,不同的命令做不同的实现
  • ConcreteCommand(具体命令类):实现抽象命令,做具体的实现
  • Receiver(接受者):真正执行命令的对象
  • Invoker(调用者/请求者):请求的封装发送者,它通过命令对象来执行请求,不会直接操作接受者,而是直接关联命令对象,间接调用到接受者的相关操作。
  • Client(客户端):一般我们在客户端中创建调用者对象,具体的命令类,去执行命令。

命令模式的UML图如下:

命令模式的实现

下面我们模拟一下智能音响的场景:

先创建一个空调对象,也就是Receiver(接受者),它才是真正的操作对象:

public class AirConditionerReceiver {
    public void turnOn(){
        System.out.println("打开空调...");
    }

    public void turnOff(){
        System.out.println("关闭空调...");
    }
}

抽象命令类如下:

public interface Command {
    void execute();
}

打开TurnOnCommand以及关闭TurnOffCommand空调的具体实现类:

public class TurnOnCommand implements Command{
    private AirConditionerReceiver airConditionerReceiver;

    public TurnOnCommand(AirConditionerReceiver airConditionerReceiver) {
        super();
        this.airConditionerReceiver = airConditionerReceiver;
    }


    @Override
    public void execute() {
        airConditionerReceiver.turnOn();
    }
}
public class TurnOffCommand implements Command{
    private AirConditionerReceiver airConditionerReceiver;

    public TurnOffCommand(AirConditionerReceiver airConditionerReceiver) {
        super();
        this.airConditionerReceiver = airConditionerReceiver;
    }


    @Override
    public void execute() {
        airConditionerReceiver.turnOff();
    }
}

Invoker调用者,/请求者(智能音响):

public class Invoker {
    private Command command;

    public Invoker(Command command) {
        this.command = command;
    }

    public void action(){
        System.out.print("小度智能家居为你服务 --> ");
        command.execute();
    }
}

客户端测试一下:

public class Client {
    public static void main(String[] args) {
        Command command = new TurnOnCommand(new AirConditionerReceiver());
        Invoker invoker = new Invoker(command);
        invoker.action();

        Command turnOffCommand = new TurnOffCommand(new AirConditionerReceiver());
        Invoker turnOff = new Invoker(turnOffCommand);
        turnOff.action();
    }
}

测试结果如下:

小度智能家居为你服务 --> 打开空调...
小度智能家居为你服务 --> 关闭空调...

通过以上的测试,我们确实通过命令传递,就可以操作空调,客户端也没有直接关联空调,如果需要其他操作,那么另外实现一个命令实现类即可,拓展比较方便,不同命令之间不会相互影响。

命令模式的拓展

  1. 多条命令

如果我们需要执行多条命令,那么可以考虑在内部维护一个列表,添加之后依次执行即可。

  1. 维护日志

如果考虑到执行命令的日志,我们则需要将对象序列化保存起来(磁盘上),维护好执行的状态,在系统故障的时候,可以从断开的地方继续执行。

  1. 撤销

如果某个命令需要撤销,那么我们需要在命令的抽象类里面加一个undo()方法,类似于Mysql数据库的undo操作,执行它即可撤销操作,当然这个中间过程涉及到了状态的维护,细节需要具体的命令来实现。(类似于数据库的事务回滚)

优缺点

优点:

  • 降低系统耦合度
  • 易于拓展新的命令

缺点:

  • 具体命令如果过多,类数量爆炸

命令模式主要是想让请求者与真正的接受者解耦合,其实用的伎俩也是加一层(命令层)的操作,有一句话说得好,不是我说的:

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。

这句话几乎概括了计算机软件体系结构的设计要点.整个体系从上到下都是按照严格的层级结构设计的,体现了设计的层次感,而不是相互缠绕。个人觉得之所以这样设计,最根本的原因在于社会分工以及人类大脑思维对于分层体系认知能力比较强,毕竟代码是不同人写给不同人读的

【作者简介】
秦怀,公众号【秦怀杂货店】作者,个人网站:http://aphysia.cn,技术之路不在一时,山高水长,纵使缓慢,驰而不息。

剑指Offer全部题解PDF

开源编程笔记

设计模式系列:

posted @ 2022-02-11 09:01  第十六封  阅读(107)  评论(0编辑  收藏  举报