设计模式——2_1 命令(Command)

文章目录

  • 定义
  • 图纸
  • 一个例子:空调和他的遥控器
    • 只有控制面板的空调
    • 遥控器
    • 可以撤销的操作
  • 碎碎念
    • 命令和Runnable
    • 命令和事务

定义

把请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作

在职责链中,我们把不同的动作分支组合在一起,让请求在不同的分支中进行流通,他可以是逻辑上的流通,也可以是封装成一个参数对象在里面流通。而在命令模式中,这种思想进一步升级,他会不分青红皂白的把所有请求封装成命令对象



图纸

在这里插入图片描述

各位道友应该知道有个叫枪的东西吧,一种装上子弹就可以开火的设备

和由枪和子弹构成的模式一样,命令模式是由 实际执行操作的【Receiver】命令对象【ConcreteCommand】 构成,如果把 Receiver 看成 ,那么 ConcreteCommand 就是 子弹

每次要开枪执行指令)之前,你都要获取子弹获取对应的 ConcreteCommand 对象),上膛组合 Receiver 和 ConcreteCommand 对象),最后扣动扳机调用 Receiver 的 Action 方法



一个例子:空调和他的遥控器

假定你入职了一家空调公司,接手了和立式空调有关的模块。不久之后,公司决定给这个立式空调增加一个遥控器(原有的立式空调全部都是通过控制面板上的按钮进行操作的)。正当你摩拳擦掌准备大干一番的时候,前辈留给你的紧耦合代码却让你差点当场骂娘。困难面前,你是要说服上司放弃遥控器的企划,还是连夜删库跑路?又或者重构已有的代码……

铺垫有点太长了,总之这次的例子开始了:


只有控制面板的空调

言归正传,前辈留下的代码是这样的:

在这里插入图片描述

AirConditioner(空调)

/*** 空调*/
public class AirConditioner {public static final int MAX_TEMPERATURE = 32;//最高温度public static final int MIN_TEMPERATURE = 18;//最低温度private static final String[] MODE_ARRAY = {"制冷模式", "睡眠模式", "送风模式"};/*** 温度*/private Float temperature;/*** 当前模式*/private Integer modePoint;/*** 是否是开启的*/private boolean isOn;/*** 控制面板*/private ControlPanel controlPanel;public AirConditioner() {controlPanel = ControlPanel.createControlPanel(this);}public static String[] getModeArray() {return MODE_ARRAY.clone();}public Float getTemperature() {return temperature;}public void setTemperature(Float temperature) {if (temperature == null || (temperature >= MIN_TEMPERATURE && temperature <= MAX_TEMPERATURE)) {this.temperature = temperature;}}public String getMode() {return MODE_ARRAY[modePoint];}public Integer getModePoint() {return modePoint;}public void setModePoint(Integer modePoint) {this.modePoint = modePoint;}public boolean isOn() {return isOn;}public void setOn(boolean on) {isOn = on;}
}

ControlPanel

/*** 控制面板*/
public class ControlPanel {private final AirConditioner airConditioner;private ControlPanel(AirConditioner airConditioner) {this.airConditioner = airConditioner;}public static ControlPanel createControlPanel(AirConditioner airConditioner) {ControlPanel controlPanel = new ControlPanel(airConditioner);controlPanel.off();return controlPanel;}/*** 开机*/public void on() {if (!airConditioner.isOn()) {//关机模式才可以执行这个动作airConditioner.setOn(true);//设定开机airConditioner.setTemperature(26f);//默认26度airConditioner.setModePoint(0);//默认第一个模式}}/*** 关机*/public void off() {if (airConditioner.isOn()) {//开机状态才可以执行这个动作airConditioner.setOn(false);airConditioner.setTemperature(null);airConditioner.setModePoint(null);}}/*** 温度上升1*/public void addTemperature() {if (airConditioner.isOn()) {airConditioner.setTemperature(airConditioner.getTemperature() + 1);}}/*** 温度下降1*/public void lessenTemperature() {if (airConditioner.isOn()) {airConditioner.setTemperature(airConditioner.getTemperature() - 1);}}/*** 下一模式*/public void nextMode() {Integer modePoint = airConditioner.getModePoint();if (airConditioner.isOn()) {if (modePoint + 1 >= AirConditioner.getModeArray().length) {airConditioner.setModePoint(modePoint + 1);} else {airConditioner.setModePoint(0);}}}
}

在这个只有控制面板的立式空调里面,前辈通过 AirConditioner(空调) 来表示一部空调的底层函数,然后通过 ControlPanel(控制面板)Client 提供操作空调内部属性的接口

虽然它可以如预期一般完成任务,但这种设计绝算不上优雅,他的问题主要体现在 控制面板 和空调底层方法之间的耦合过于紧密,现在只有一种类型的空调,如果有底层方法不相同的第二种类型的空调出现,那么这个 控制面板 是无法与其兼容的


遥控器

遥控器 的引入改变了现态,很显然,遥控器 至少需要拥有和 控制面板 一样的效果,而且一个 遥控器 必须可以同时对应多个 空调 对象。也就是说,你只有在最终调用遥控器上面的任务的时候才会知道到底要调用哪个空调上的方法,做出来的效果应该是这样的:

在这里插入图片描述

我们新增了 RemoteController(遥控器) 作为遥控器的实现,而 RemoteControllerControlPanel 的实现唯一的区别就在于 ControlPanelAirConditioner 从他诞生的时候就被定义且无法修改,而 RemoteControllerAirConditioner 在调用命令的时候才会被指定

除此之外,程序中出现了大量的重复代码,这些重复代码分布在 控制面板遥控器 的每一个对应方法中

有没有办法把他们解耦?我的意思是把调用者和他的实现解耦,也就是说做成这样的效果:

在这里插入图片描述


放在本例中,他长这样:

在这里插入图片描述

Executor & Pool

/*** 空调命令执行器*/
public abstract class ACExecutor implements Cloneable {/*** 执行命令*/public abstract void execute(AirConditioner airConditioner);/*** 克隆方法,覆盖Object中的clone*/public ACExecutor clone() {try {return (ACExecutor) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();throw new RuntimeException(e);//异常原样抛出}}
}public class OnExecutor extends ACExecutor {@Overridepublic void execute(AirConditioner airConditioner) {if (!airConditioner.isOn()) {//关机模式才可以执行这个动作airConditioner.setOn(true);//设定开机airConditioner.setTemperature(26f);//默认26度airConditioner.setModePoint(0);//默认第一个模式}}
}public class OffExecutor extends ACExecutor {@Overridepublic void execute(AirConditioner airConditioner) {if (airConditioner.isOn()) {//开机状态才可以执行这个动作airConditioner.setOn(false);airConditioner.setTemperature(null);airConditioner.setModePoint(null);}}
}public class AddTemperatureExecutor extends ACExecutor {@Overridepublic void execute(AirConditioner airConditioner) {if (airConditioner.isOn()) {airConditioner.setTemperature(airConditioner.getTemperature() + 1);}}
}public class LessenTemperatureExecutor extends ACExecutor {@Overridepublic void execute(AirConditioner airConditioner) {if (airConditioner.isOn()) {airConditioner.setTemperature(airConditioner.getTemperature() - 1);}}
}public class NextModeExecutor extends ACExecutor {@Overridepublic void execute(AirConditioner airConditioner) {Integer modePoint = airConditioner.getModePoint();if (airConditioner.isOn()) {if (modePoint + 1 >= AirConditioner.getModeArray().length) {airConditioner.setModePoint(modePoint + 1);} else {airConditioner.setModePoint(0);}}}
}/*** 空调命令执行器的对象池*/
public class ACExecutorPool {//单例相关private static final ACExecutorPool INSTANCE = new ACExecutorPool();public static ACExecutorPool getInstance() {return INSTANCE;}//原型池private final Map<Class<? extends ACExecutor>, ACExecutor> prototypePool = new HashMap<>();public ACExecutor createOnExecutor() {return getNewObjectByPool(OnExecutor.class);}public ACExecutor createOffExecutor() {return getNewObjectByPool(OffExecutor.class);}public ACExecutor createAddTemperatureExecutor() {return getNewObjectByPool(AddTemperatureExecutor.class);}public ACExecutor createLessenTemperatureExecutor() {return getNewObjectByPool(LessenTemperatureExecutor.class);}public ACExecutor createNextModelExecutor() {return getNewObjectByPool(NextModeExecutor.class);}/*** 从原型池中获取对应的新命令对象*/private ACExecutor getNewObjectByPool(Class<? extends ACExecutor> c) {if (prototypePool.containsKey(c)) {return prototypePool.get(c).clone();} else {try {ACExecutor prototype = c.newInstance();prototypePool.put(c, prototype);return prototype.clone();} catch (InstantiationException | IllegalAccessException e) {e.printStackTrace();throw new RuntimeException("初始化命令对象原型池的时候出现了异常,具体异常为:" + e.getCause(), e);}}}
}

ControlPanel & RemoteController

/*** 控制面板*/
public class ControlPanel {private final AirConditioner airConditioner;private ControlPanel(AirConditioner airConditioner) {this.airConditioner = airConditioner;}public static ControlPanel createControlPanel(AirConditioner airConditioner) {ControlPanel controlPanel = new ControlPanel(airConditioner);controlPanel.off();return controlPanel;}/*** 开机*/public void on() {ACExecutorPool.getInstance().createOnExecutor().execute(airConditioner);}/*** 关机*/public void off() {ACExecutorPool.getInstance().createOffExecutor().execute(airConditioner);}/*** 温度上升1*/public void addTemperature() {ACExecutorPool.getInstance().createAddTemperatureExecutor().execute(airConditioner);}/*** 温度下降1*/public void lessenTemperature() {ACExecutorPool.getInstance().createLessenTemperatureExecutor().execute(airConditioner);}/*** 下一模式*/public void nextMode() {ACExecutorPool.getInstance().createNextModelExecutor().execute(airConditioner);}
}/*** 遥控器*/
public class RemoteController {/*** 开机*/public void on(AirConditioner airConditioner) {ACExecutorPool.getInstance().createOnExecutor().execute(airConditioner);}/*** 关机*/public void off(AirConditioner airConditioner) {ACExecutorPool.getInstance().createOffExecutor().execute(airConditioner);}/*** 温度上升1*/public void addTemperature(AirConditioner airConditioner) {ACExecutorPool.getInstance().createAddTemperatureExecutor().execute(airConditioner);}/*** 温度下降1*/public void lessenTemperature(AirConditioner airConditioner) {ACExecutorPool.getInstance().createLessenTemperatureExecutor().execute(airConditioner);}/*** 下一模式*/public void nextMode(AirConditioner airConditioner) {ACExecutorPool.getInstance().createNextModelExecutor().execute(airConditioner);}
}

在这种实现方式里,我们把具体执行的操作封装到 ACExecutor(空调执行器) 类簇中,为 所有的操作定义了自己的执行类;然后在 client 点击 控制面板遥控器 上的对应按钮的时候,把让他们获取对应的 执行器对象,并执行对应的操作。而 控制面板遥控器 根本不关心 执行器 的底层做了什么操作,这让他们从命令的执行者变成了命令的调度者


听起来很复杂,但是这种复杂是 颗粒度 细致程度导致的(五个命令导致我们需要五个对应的执行者子类),流程上其实是很简单的。以开机为例,我们的程序其实是这样做的:

在这里插入图片描述

这种让程序变得复杂的写法是有价值的,至少可以让你在以下两种情况可以少写很多代码:

  1. 当出现新的操作面板,比如手机APP控制空调的时候,我就可以通过创建新的控制器类并让他调用已有的执行者来实现,而不需要再重复执行者里面的代码
  2. 如果出现了接口一致,但行为不一致的空调,那我依然可以继续使用控制面板和遥控器,只需要建立对应的执行器类簇即可

而这正是一个标准的命令实现


可以撤销的操作

一般我讲到 ”这正是xxx的实现“ 的时候,例子就结束了,但这次是例外

请留意一下上例的一个配角类—— ACExecutorPool,这个类的作用是为我们的程序产出可靠的执行类对象。在上例中,这一个类里,用到了两种模式,分别是 单例原型

单例很好理解,为了让全局都用一个对象池

为什么要用原型呢?每个执行器都用单例不是更节省吗?


这种情况下会用原型只有一种可能,那就是执行器应该是带状态的,而且他的状态是有意义的

那你会说了,不对啊,上例的执行器哪有状态。别急,业务来了


某日,接到通知,我们需要在 控制面板 上添加一个 返回(back) 按钮,用于撤销我们刚刚执行的命令,要怎么实现呢?

如果你没有使用命令模式实现这种功能费死劲,但是在命令模式的框架下,你可以这样做:

在这里插入图片描述

我们在 ACExecutor 中提供了用于回滚的方法 back,而在 控制面板 中我们通过添加 history 列表的方式存储已经执行过的执行器,以便我们回滚

这时候 ACExecutor 的对象就绝不能用单例了,因为他的属性是一种凭证,用于证明这个执行器对象有没有执行成功。此时原型模式就是你比较合适的选择了,因为执行器对象是会被大量创建的,原型可以有效的降低创建执行器的开销(复制初始属性

而这也是命令模式所能实现的功能之一



碎碎念

命令和Runnable

Java的多线程模块中用到了很标准的命令模式

我们通过 Thread 来管控线程,但是线程具体如何执行是由 Runnable 来决定的

也就是说 Thread 本质上其实就是 ReceiverRunnable 才是掌握具体内容的执行器


命令和事务

命令模式中的执行器的颗粒度你是可以自己掌握的,你可以只让他执行单体命令,也可以让他执行多个指令捆绑在一起的复合指令

这就是SQL中的事务一样,他是一个具有 原子性 的整体,一荣俱荣,一损俱损




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/665071.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux文件结构

所有Linux系统的文件结构都是一样的&#xff0c;区别于win系统的c盘&#xff0c;d盘&#xff0c;他只有一个根目录“/”&#xff0c;下面的文件夹结构基本是一样的&#xff0c;如下&#xff0c;文件夹有“箭头”代表的是软链接&#xff0c;即该文件夹的位置不在此&#xff0c;右…

小米平板6获取root权限教程

1. 绑定账号 1> 打开"设置-我的设备-全部参数-连续点击MIUI版本按钮"&#xff0c;直到提示已打开开发者模式( p s : 这里需要重点关注红框平板型号和 M I U I 版本&#xff0c;例如我这里平板型号是 X i a o m i P a d 6 &#xff0c; M I U I 版本是 14.0.10 &am…

【开源】JAVA+Vue+SpringBoot实现就医保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

酷开系统 | 酷开科技智慧AI带你领略神奇的世界

在这个科技日新月异的时代&#xff0c;AI已成为我们生活中不可或缺的一部分。它不仅改变了我们的生活方式&#xff0c;更让我们对未来充满期待。说起酷开系统中智慧AI的强大&#xff0c;着实让人叹为观止。无论是语音识别、数据整理还是语言处理&#xff0c;智慧AI都在不断地突…

MySQL数据库①_MySQL入门(概念+使用)

目录 1. 数据库的概念 1.1 数据库的存储介质 1.2 主流数据库 2. MySQL的基本使用 2.1 链接数据库 2.2 服务器管理 2.3 数据库&#xff0c;服务器和表关系 2.4 简单MySQL语句 3. MySQL架构 4. SQL分类 5. 存储引擎 本篇完。 1. 数据库的概念 数据库是按照数据结构来…

测试大佬是怎么看待测试用例设计的

前言 最近干的最多的事情就是设计测试用例、评审测试用例了&#xff0c;于是我不禁又想到了一个经典的问题&#xff1a;如何设计出优秀的测试用例&#xff1f; 可能有些童鞋看到这个问题会有些不以为然&#xff0c;这有什么好想的&#xff1f;干个测试谁还不会设计测试用例&…

C++(10)——类与对象(最终篇)

目录 static成员 概念 特性 友元 友元函数 友元类 内部类 匿名对象 经过这么多天的分享&#xff0c;C的类与对象终于要结束了。结束也意味着C快要入门了。 static成员 概念 声明为static的类成员称为类的静态成员&#xff0c;用static修饰的成员变量&#xff0c;称之…

基于YOLOv8算法的照片角度分类项目实践

目录 一、任务概述二、YOLOv8算法简介2.1 算法改进2.2 算法特点2.3 网络结构2.4 性能比较 三、工程实践3.1 安装算法框架库ultralytics3.2 库存照片预处理3.2.1 提取所有图片3.2.2 去除冗余的相同照片3.2.3 去除无车辆照片3.2.4 随机提取指定数量的图片 3.3 照片朝向分类3.3.1 …

STM32--USART串口(2)串口外设

一、USART简介 可配置数据位&#xff1a;不需要校验就是8位&#xff0c;需要校验就选9位&#xff1b; 停止位&#xff1a;决定了帧的间隔; STM32F103C8T6USART&#xff1a;USART1挂载在APB2总线上&#xff0c;USART2和USART3挂载在APB1总线上&#xff1b; 二、USART框图 TXE…

excel给数据库初始化/旧数据处理(自动sql拼装)

思路&#xff1a; 首先导出数据到excel编写单条数据操作的sql利用excel CONCATENATE 函数自动生成&#xff0c;每一行数据的操作sql 小技巧:对于需要套娃的字段值&#xff0c;可以加一个临时列同样使用CONCATENATE函数进行sql拼装 案例&#xff1a; 1.临时列:CONCATENATE(C2, …

分库分表 21 条法则,hold 住!

大家好&#xff5e;今天给大家分享分库分表的 21 条法则 我们结合具体业务场景&#xff0c;以t_order表为例进行架构优化。由于数据量已经达到亿级别&#xff0c;查询性能严重下降&#xff0c;因此我们采用了分库分表技术来处理这个问题。具体而言&#xff0c;我们将原本的单库…

【Python小游戏】五子棋小游戏(完整代码)

文章目录 写在前面Tkinter简介五子棋小游戏游戏介绍程序设计运行结果注意事项写在后面写在前面 本期内容:基于tkinter开发一个五子棋小游戏 实验环境 python3.11及以上pycharmtkinterTkinter简介 Tkinter是Python中最常用的图形用户界面(GUI)库之一,用于创建窗口、对话框…

如何搭建私有云盘SeaFile并实现远程访问本地文件资料

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-hsDnDEybLME85dTx {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

Web项目利用OSS进行图像存储服务

一、OSS介绍 在Web项目中&#xff0c;一些常见的功能&#xff0c;比如展示图片&#xff0c;修改头像等&#xff0c;都需要进行图片的上传操作&#xff0c;但是如果是存储在Web服务器中&#xff0c;在读取图片的时候会占用比较多的资源&#xff0c;影响服务器的性能。 常…

【数据结构】双向带头循环链表实现及总结

简单不先于复杂&#xff0c;而是在复杂之后。 文章目录 1. 双向带头循环链表的实现2. 顺序表和链表的区别 1. 双向带头循环链表的实现 List.h #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h>typede…

fastDFS客户端实现文件上传

一、准备工作 请确保fastDFS的tracker服务和storage服务都是处于启动状态&#xff0c;防火墙是关闭的&#xff1b; 二、具体步骤 1、pom.xml 2、让当前的微服务成为fdfs的客户端 package com.qf.config;import com.github.tobato.fastdfs.FdfsClientConfig; import org.sprin…

JMeter HTTP请求的详细指南,还不知道的快来看

HTTP请求简介 在JMeter中&#xff0c;服务器名称和它的路径对于检查请求是否到达了正确的目的地非常重要。默认情况下&#xff0c;HTTP协议与请求一起被遵循&#xff0c;如果需要&#xff0c;可以转换为HTTPS。如果需要&#xff0c;用户参数可以包含在特定页面的请求中。如果&a…

MySQL查询缓存

MySQL查询缓存 MySQL在查询的时候首先会查询缓存&#xff0c;如果缓存命中的话就直接返回结果&#xff0c;不需要解析sql语句&#xff0c;也不会生成执行计划&#xff0c;更不会执行&#xff1b;如果没有命中缓存&#xff0c;则再进行SQL解析以及进行查询&#xff0c;并将结果返…

机器学习系列-2 线性回归训练损失

机器学习系列-2 线性回归&训练损失 学习内容来自&#xff1a;谷歌ai学习 https://developers.google.cn/machine-learning/crash-course/framing/check-your-understanding?hlzh-cn 本文作为学习记录1 线性回归&#xff1a; 举例&#xff1a;蝉&#xff08;昆虫物种&…

安装配置sqoop

一、了解Sqoop 1、Sqoop产生的原因 A. 多数使用hadoop技术的处理大数据业务的企业,有大量的数据存储在关系型数据中。 B. 由于没有工具支持,对hadoop和关系型数据库之间数据传输是一个很困难的事。 以上是sqoop产生的主要原因,也因此Sqoop主要用于hadoop与关系型数据库之…