设计模式——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.hqwc.cn/news/449402.html

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

相关文章

Visual Studio 2022 查看类关系图

这里写自定义目录标题 右键要查看的项目 -“查看”-“查看类图”效果展示&#xff1a; 原文地址 www.cnblogs.com 步骤1&#xff1a;勾选扩展开发 步骤2: 勾选类设计器 右键要查看的项目 -“查看”-“查看类图” 效果展示&#xff1a;

Megatron-LM源码系列(七):Distributed-Optimizer分布式优化器实现Part2

1. 使用入口 DistributedOptimizer类定义在megatron/optimizer/distrib_optimizer.py文件中。创建的入口是在megatron/optimizer/__init__.py文件中的get_megatron_optimizer函数中。根据传入的args.use_distributed_optimizer参数来判断是用DistributedOptimizer还是Float16O…

ChatGPT的探索与实践-应用篇

这篇文章主要介绍在实际的开发过程当中&#xff0c;如何使用GPT帮助开发&#xff0c;优化流程&#xff0c;文末会介绍如何与618大促实际的业务相结合&#xff0c;来提升应用价值。全是干货&#xff0c;且本文所有代码和脚本都是利用GPT生成的&#xff0c;请放心食用。 场景一&…

Postman(接口测试工具),什么是Postman接口

目录 一.基本介绍 Postman 是什么Postman 快速入门快速入门需求说明 二.Postman 完成 Controller 层测试 需要的代码&#xff1a; Java类request.jspsuccess.jsp1. 完成请求2. 完成请求3. 完成请求4. 完成请求5. 完成请求 三.发送join 目录 一.基本介绍 Postman 是什么 …

超详细Anconda pytorch cuda cuDNN安装及介绍(李沐老师视频环境)

零、准备知识阶段 ⇲ 显卡驱动、CUDA、cuDNN之间联系以及安装配置 在配置PyTorch的过程中&#xff0c;显卡驱动、CUDA、cuDNN三者之间的关系、作用以及在众多版本中如何搭配一直困扰着我。虽然网上资料很多&#xff0c;但各说其词&#xff0c;即使最终迈过种种坑成功运行&…

idea项目如何上传gitee

1.先创建仓库&#xff08;nonono&#xff01;&#xff01;&#xff01;idea上传会自动创建仓库&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 2.从gitee上面clone下来&#xff08;nonono&#xff01;&#xff01;&#xff01;&#xff01;这个.git文件也是自动…

Flutter 各种Demo效果合集

Flutter 各种Demo实现效果&#xff1a; github&#xff1a;GitHub - PangHaHa12138/FlutterDemo: Flutter 各种Demo效果合集 1&#xff1a;2种 仿朋友圈 效果,顶部拉伸 和 不拉伸 2&#xff1a;仿抖音上下滑动视频播放 3&#xff1a;视频直播&#xff08;使用的电视台的m3u…

事件分发机制:从OnTouchListener开始,按钮变色的Demo

要彻底弄清楚事件分发机制&#xff0c;先要明白OnTouchListener的作用。 我们看下Android 1.6上&#xff0c;OnTouchListener的代码定义&#xff0c;源码在线地址&#xff1a;Android 1.6 sdk4 View.java 可以看到&#xff0c;OnTouchListener就是View类中的一个public接口&am…

如何读论文

如何读论文 0. 目的 单篇文章从头读到尾&#xff0c;可以&#xff1b; 世界上那么多篇文章&#xff0c; 都这样读&#xff0c; 时间上划不来。 适合你的文章就那么一小撮。 paper 的八股文结构&#xff1a; titleabstractintromethodexpconclusion 1. 第一遍 海选&#…

运用 StringJoiner 高效的拼接字符串

运用 StringJoiner 高效的拼接字符串 package com.zhong.stringdemo;import java.util.ArrayList; import java.util.StringJoiner;public class Test {public static void main(String[] args) {ArrayList<String> s new ArrayList<>();s.add("11");s.…

CEC2013(python):五种算法(OOA、WOA、GWO、DBO、HHO)求解CEC2013(python代码)

一、五种算法简介 1、鱼鹰优化算法OOA 2、鲸鱼优化算法WOA 3、灰狼优化算法GWO 4、蜣螂优化算法DBO 5、哈里斯鹰优化算法HHO 二、5种算法求解CEC2013 &#xff08;1&#xff09;CEC2013简介 参考文献&#xff1a; [1] Liang J J , Qu B Y , Suganthan P N , et al. Pro…

跟着cherno手搓游戏引擎【19】抽象纹理

引入&#xff1a; 导入stb_image: GitHub - nothings/stb: stb single-file public domain libraries for C/C 下载复制stb_image.h的内容&#xff08;8000多行&#xff09;&#xff0c;然后粘到如图位置 stb_image.cpp: #include"ytpch.h" #define STB_IMAGE_IM…