设计模式-命令模式

2023-09-17 15:55:51

“小度,小度,热死了请打开空调~”,“小度,小度,主人回家了~”,“小度,小度,播放一首炸雷~”,像小度类似的智能产品正在逐步改善我们的生活,只要我们一声令下,产品立马执行,毫不拖延。就如同老板的命令一样,系统需接收到命令后经过一系列分析后,会自动选择执行哪些命令,而发令者完全不关心内部是执行了哪些动作以及哪个具体方法。比如“小度的主人回家了”,小度内部接受到指令后会自动分析出需要打开灯光,打开窗帘,打开中央空调,播放一首著名的萨克斯独奏-回家以及检查房门是否已关闭等内容。这一切都是不需要发令者感知的 ,发令者只需要发出指令,具体该执行哪些功能由指令控制器来分析完成。
对于这样的场景下,如何从代码层面上很好的解决呢?我的第一个想法那就是封装,面向对象那些事情都离不开封装。封装系统向外透出的功能,我们很自然能够想到“接口”、“API”这些概念。发送者发送的指令可以是由系统提供一系列API,比如电商系统的提单就调用类似/order/submit的API,加入购物车就调用/cart/add/product/的API。这些API的具体执行逻辑在调用方也是解耦(不感知)的,其内部流程化逻辑也是多个系统功能之间的紧密配合。但尽管如此,API或接口的这种方式依然不能够解决“小度”类似场景的问题。为何呢?这是因为小度的指令执行不是流程化的东西,具体的说就是一条指令可能是“打开空调”,也可能是“打开空调,打开灯光”,也可能是“打开灯光,关闭窗帘”,这种功能之间的组合是非流程化的东西,无法穷尽就不适合通过API、接口进行封装。更甚者我如果说“小度,小度,撤销上一次的操作”,我想问问你使用API、接口能完成这项工作吗?明显不能。但是我们今天要讲述的**命令模式(Command Pattern)**就可以。

一、命令模式的概念

命令模式,又称作动作模式或事务模式。其一般定义(稍有改动)如下:

将请求封装为命令对象,使得客户端对系统的请求参数化、具体请求可以在运行时更改、排队、记录,并且能够提供命令的撤销和恢复功能。

命令模式定义中,关于第一句我个人认为有思考的空间。我认为封装的不应该是客户端请求,而是系统所提供的一系列可执行业务逻辑,比如上文中“灯光控制逻辑”、“窗帘控制逻辑”等。封装客户端请求注定会带来很多扩展性问题,并且最关键的是你根本无法预知客户端会如何请求,以及请求哪些功能。最后带来的问题必然是无法支持客户端变化无端的需要,需要不端增加对应的组合命令对象,引起类的剧增。所以,我认为应该将可执行单元封装为命令对象。
系统所提供的的功能向外透出为一系列的可执行的命令对象,而客户端只需要通过参数告知系统应该执行哪些命令,或通过其他可解析参数的方式(由系统解析为指令)来完成请求即可。
既然系统功能已经封装为一个个命令(Command )组成,那么这些命令自然就涉及到运行时更改(全部取消或暂停执行)、排队(多个未执行命令排队顺序执行)、记录(记录下历史已执行的命令)、撤销(取消上一次执行的命令)、恢复(恢复上一次执行的命令)等操作。那这部分职责就应该由命令执行器(Command Executor)来负责。
概念总结:

  • 命令模式能够解决客户端多样化功能组合请求,且需具备事务回滚处理能力的场景。
  • 命令模式将系统功能拆分独立命令,客户端发出命令来执行其所需要的功能
  • 命令模式将系统功能逻辑及客户端请求之间进行了解耦。Command仅需关注本身业务逻辑的实现,而客户端解析命令以及命令之间的相关调配由CommandExecutor负责。

二、应用实践

为了更好的理解、应用命令模式,本章将给出一个能够使用命令模式解决相关问题的案例。
案例背景就是关于上文中提到的小度智能家居的例子。小度系统涉及的功能包括开关机控制模块、灯光控制模块,窗帘控制模块,以及门锁检查模块(为简化代码,其他功能暂不考虑)。为了更好处理主人的命令,小度系统还需要提供一个智能控制器来负责命令之间的排队、撤销、恢复、解析等功能。
命令接口:

public interface Command {
    // 执行当前命令
    void execute();
    // 恢复当前命令--命令需具备回滚能力
    void undo();
}

打开灯光命令:

public class TurnOnLightCommand implements Command {

    public static final TurnOnLightCommand turnOnLightCommandIns = new TurnOnLightCommand();

    private final LightControl lightControl;

    public TurnOnLightCommand() {
        this.lightControl = new LightControl();
    }

    @Override
    public void execute() {
        lightControl.turnOn();
    }

    @Override
    public void undo() {
        lightControl.turnOff();
    }
}

关闭灯光命令:

public class TurnOffLightCommand implements Command {

    public static final TurnOffLightCommand turnOffLightCommandIns = new TurnOffLightCommand();

    private final LightControl lightControl;

    public TurnOffLightCommand() {
        this.lightControl = new LightControl();
    }

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

    @Override
    public void undo() {
        lightControl.turnOn();
    }
}

打开窗帘命令:

public class OpenCurtainCommand implements Command {

    public static final OpenCurtainCommand openCurtainCommandIns = new OpenCurtainCommand();

    private final CurtainControl curtainControl;

    public OpenCurtainCommand() {
        this.curtainControl = new CurtainControl();
    }

    @Override
    public void execute() {
        curtainControl.open();
    }

    @Override
    public void undo() {
        curtainControl.close();
    }
}

关闭窗帘命令:

public class CloseCurtainCommand implements Command {

    public static final CloseCurtainCommand closeCurtainCommandIns = new CloseCurtainCommand();

    private final CurtainControl curtainControl;

    public CloseCurtainCommand() {
        this.curtainControl = new CurtainControl();
    }

    @Override
    public void execute() {
        curtainControl.close();
    }

    @Override
    public void undo() {
        curtainControl.open();
    }
}

智能家居控制器代码实现:

/**
 * 智能家居控制系统
 */
public class SmartHomeControl {

    private final Queue<Command> commandQueue;      // 命令队列

    public SmartHomeControl() {
        commandQueue = new LinkedList<>();
    }

    public void request(String strCommand) {
        // 先解析出命令列表
        List<Command> commands = parseCommand(strCommand);
        this.request(commands);
    }

    public void request(List<Command> commands) {
        if(commands == null || commands.isEmpty()) {
            return;
        }
        commandQueue.addAll(commands);      // 添加到队列中等待执行
    }

    public void executorCommands() {        // 逐个执行队列中的命令
        while (!commandQueue.isEmpty()) {
            Command command = commandQueue.poll();
            command.execute();
        }
    }

    public void undorCommands() {           // 逐个取消队列中的命令
        while (!commandQueue.isEmpty()) {
            Command command = commandQueue.poll();
            command.undo();
        }
    }

    private List<Command> parseCommand(String strCommand) {
        // 根据strCommand解析出对应的指令列表
        return new ArrayList<>();
    }
}

客户端执行打开灯光,和恢复指令操作:

public class Client {
    public static void main(String[] args) {
        // 创建智能家居模块
        SmartHomeControl smartHomeControl = new SmartHomeControl();

        // 发出打开窗帘命令
        Command turnOnLightCommand = TurnOnLightCommand.turnOnLightCommandIns;

        smartHomeControl.request(Collections.singletonList(turnOnLightCommand));
        smartHomeControl.executorCommands();        // 执行命令

        smartHomeControl.request(Collections.singletonList(turnOnLightCommand));
        smartHomeControl.undorCommands();           // 执行取消命令
    }
}

在这里插入图片描述
从代码及类图上可以看出,通过将各个业务逻辑单元封装为一个个Command类,由SmartHomeControl类来负责命令之间的入队、执行等操作,实现了命令调用者与实际业务逻辑之间的解耦合,提高了重用的方法。客户端可以任意选择执行不同命令的组合。
命令模式的优点:

  • 将调用者与具体执行者解耦
  • 支持将多个命令装配为一个复合命令
  • 支持操作的事务能力
  • 通过命令控制器可以做很多东西,比如限流、拦截器、校验以及日志记录等等
  • 符合开闭原则,新增命令很容易,不会影响原有功能

命令模式的缺点:

  • 系统功能逐步复杂时,命令类可能会很多。
更多推荐

如何修复msvcp140.dll文件,msvcp140.dll丢失的解决方法

在使用电脑的过程中,可能会遇到提示“msvcp140.dll丢失”的错误。这通常是由于某些程序或游戏在运行时需要调用msvcp140.dll文件,但由于某种原因(如病毒感染、误删等),该文件被删除或损坏,导致程序无法正常运行。解决方法1.重新安装相关程序当出现msvcp140.dll丢失的错误时,首先尝试重新安装出现问

Haproxy集群与常见的Web集群调度器

文章目录1.Web集群调度器概述1.1Web集群调度器简介1.2调度器类别1.2.1常用软件类1.2.2常用硬件类2.Haproxy软件介绍2.1Haproxy简介2.2支持功能2.3主要特性2.4常用调度算法2.4.1轮询:RR(RoundRobin)2.4.2最小连接数:LC(LeastConnections)2.

MongoDB-1入门介绍

NoSQLNoSQL(NoSQL=NotOnlySQL),意即反SQL运动,指的是非关系型的数据库优点1、对数据库高并发读写。2、对海量数据的高效率存储和访问。3、对数据库的高可扩展性和高可用性。弱点:1、数据库事务一致性需求2、数据库的写实时性和读实时性需求3、对复杂的SQL查询,特别是多表关联查询的需求简介Mong

【小沐学NLP】关联规则分析Apriori算法(Mlxtend库,Python)

文章目录1、简介2、Mlxtend库2.1安装2.2功能2.2.1UserGuide2.2.2UserGuide-data2.2.3UserGuide-frequent_patterns2.3入门示例3、Apriori算法3.1基本概念3.2apriori3.2.1示例1--生成频繁项集3.2.2示例2--选择和筛选结

nginx配置指南

nginx.conf配置找到Nginx的安装目录下的nginx.conf文件,该文件负责Nginx的基础功能配置。配置文件概述Nginx的主配置文件(conf/nginx.conf)按以下结构组织:配置块功能描述全局块与Nginx运行相关的全局设置events块与网络连接有关的设置http块代理、缓存、日志、虚拟主机等

Vulnhub实战-prime1

前言VulnHub是一个面向信息安全爱好者和专业人士的虚拟机(VM)漏洞测试平台。它提供了一系列特制的漏洞测试虚拟机镜像,供用户通过攻击和漏洞利用的练习来提升自己的安全技能。本次,我们本次测试的是prime1。一、主机发现和端口扫描查看Vmware中靶机的MAC地址,方便与之后nmap扫描出来的主机的MAC地址进行对比

第32章_瑞萨MCU零基础入门系列教程之DS18B20温度获取实验

本教程基于韦东山百问网出的DShanMCU-RA6M5开发板进行编写,需要的同学可以在这里获取:https://item.taobao.com/item.htm?id=728461040949配套资料获取:https://renesas-docs.100ask.net瑞萨MCU零基础入门系列教程汇总:https://b

Zabbix“专家坐诊”第203期问答汇总

问题一Q:用的模板,没用创建动作,但是触发器触发了我钉钉直接被通知了,怎么取消模板自带的动作?A:没有动作是不会触发事件告警的,确定是从这个平台推出的钉钉消息?或者看下zabbix的动作日志,看下能不能找到对应的消息推送记录。Q:确实没用创建动作,但是触发了我钉钉直接被通知了。A:如下,没有设定触发条件,表示任何告警都

计算机视觉面试题整理

1、介绍目标检测网络yolo系列以及ssd系列的原理,yolo对小目标检测不好的原因,除了缩小anchor外还可以如何改善?Yolo目标检测:YOLO是一种实时目标检测算法,其核心思想是将目标检测问题归为一个回归问题,直接从输入图像中预测目标的类别和位置,YOLO的主要特点有①单次前向传播(one-stage):YOL

docker-compose 中 depends_on 的作用

文章目录depends_on介绍depends_on有一个长定义模式condition说明required说明参考文档depends_on介绍在DockerCompose中,depends_on是一个用于定义服务之间依赖关系的关键字。它允许您指定一个或多个服务依赖于其他服务,以确保在启动或重新创建容器时,所依赖的服务先

第二章 进程与线程 六、线程的实现方式和多线程模型

目录一、线程的实现方式1、用户级线程2、内核级线程二、多线程模型注意:1、一对一模型(1)定义:(2)优点:(3)缺点:2、多对一模型(1)定义:(2)优点:(3)缺点:3、多对多模型(1)定义:(2)优点:(3)注意:三、线程的组织与控制总结:一、线程的实现方式1、用户级线程(1)用户级线程由应用程序通过线程库实现,

热文推荐