当前位置: 首页 > news >正文

命令模式——让程序舒畅执行

● 命令模式介绍

        命令模式(Command Pattern),是行为型设计模式之一。命令模式相对于其他的设计模式来说并没有那么多条条框框,其实并不是一个很“规矩”的模式,不过,就是基于一点,命令模式相对于其他的设计模式更为灵活多变。我们接触比较多的命令模式个例无非就是程序菜单命令,如在操作系统中,我们点击“关机”命令,系统就会执行一系列的操作,如先是暂停处理事件,保存系统的一些配置,然后结束程序进程,最后调用内核命令关闭计算机等,对于这一系列的命令,用户不用去管,用户只需要点击系统的关机按钮即可完成如上一系列的命令。而我们的命令模式其实也与之相同,将一系列的方法调用封装,用户只需要调用一个方法执行,那么所有这些被封装的方法就会被挨个调用。

● 命令模式的定义

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

● 命令模式的使用场景

        需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品。

        在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。

        需要支持取消操作。

        支持修改日志功能,这样系统崩溃时,这些修改可以被重做一遍。

        需要支持事务操作。

●  命令模式的UML类图

        UML类图如下图所示。

        根据类图可以得出如下一个命令模式的通用模式代码。

        接收者类

/*** 接收者类*/
public class Receiver {/*** 真正执行具体命令逻辑的方法*/public void action() {System.out.println("执行具体操作");}
}

        抽象命名接口

/*** 抽象命名接口*/
public interface Command {/*** 执行具体操作命令*/void execute();
}

        具体命令类

/*** 具体命令类*/
public class ConcreteCommand implements Command {private Receiver receiver;//持有一个对接收者对象的引用public ConcreteCommand(Receiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {//调用接收者的相关方法来执行具体逻辑receiver.action();}
}

        请求者类

/*** 请求者类*/
public class Invoker {private Command command;//持有一个对相应命令对象的引用public Invoker(Command command) {this.command = command;}public void action() {//调用具体命令对象的相关方法,执行具体命令command.execute();}
}

        客户类

/*** 客户类*/
public class Client {public static void main(String[] args) {//构造一个接受者对象Receiver receiver = new Receiver();//根据接受者对象构造一个命令对象Command command = new ConcreteCommand(receiver);//根据具体的对象构造请求者对象Invoker invoker = new Invoker(command);//执行请求方法invoker.action();}
}

        角色介绍。

        Receiver:接受者角色。

        该类复杂具体实施或执行一个请求,说得通俗点就是,执行具体逻辑的角色,以开头的“关机”命令操作为例,其接受者角色就是真正执行各项关机逻辑的底层代码。任何一个类都可以成为一个接受者,而在接受者类中封装具体操作逻辑的方法我们则称为行为方法。

        Command:命令角色。

        定义所有具体命令类的抽象接口。

        ConcreteCommand:具体命令角色。

        该类实现了 Command 接口,在execute 方法中调用接受者角色的相关方法,在接受者和命令执行的具体行为之间加以肉耦合。而 execute 则通常称为执行方法,如本文开头所示“关机”的操作实现,具体可能还包含很多相关的操作,比如保存数据、关闭文件、结束进程等,如果将这一系列具体的逻辑处理看作接受者,那么调用这些具体逻辑的方法就可以看作是执行方法。

        Invoker:请求者角色

        该类的职责就是调用命令对象执行具体的请求,相关的方法我们称为行动方法,还是用“关机”为例“关机”这个菜单命令一般就对应一个关机方法,我们点击了“关机”命令后,由这个关机方法去调用具体的命令执行具体的逻辑,这里的“关机”对应的这个方法就可以看做是请求者。

        Client:客户端角色

        以“关机”的例子来说就相当于人,很好理解不再多说。

        这里其实大家可以看到,命令模式的应用其实可以用一句话来概述,就是将行为调用者与实现者解耦,一个简单的例子,我们常会这样来调用代码。

        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);mPaint.setShader(new SweepGradient(canvas.getWidth() / 2, canvas.getHeight() / 2, new int[]{0xffeeadde, 0xffbc4579, 0xffe9c43f, 0xffdccb48, 0xff44adde}, null));

        这样的逻辑调用再熟悉不过,先是 new 一个 Paint 对象,紧接着调用 Paint 中的 setShader 方法为 Paint 设置一个着色器。但是,如果我们调用逻辑比较复杂或者说调用行为有多种实现时,这种将行为调用者与行为实现者耦合在一起的写法就会很有问题。这时候使用设计模式来解耦合就很有比必要,当然,这里只是作为一个引子给大家思考,不多做分析。

● 命令模式的简单实现

        命令模式总体来说并不难,只是相对比较烦琐,你想想一个简单的调用关系被解耦成多个部分,必定会增加类的复杂度,但是即便如此,命令模式的结构依然清晰。大家小时候应该玩过俄罗斯方块游戏,这里以古老的俄罗斯方块游戏为例,看看我们在命令模式下是如何操控俄罗斯方块变化的。一般来说,俄罗斯方块游戏中都有4个按钮,两个左右移动的安排,一个快速下落的按钮,还有一个变化方块形状的按钮,这是计较经典的游戏原型。一个玩游戏的人相当于我们的客户端,而游戏上的4个按钮就相当于请求者,或者也可以称为调用者,执行具体按钮命令的逻辑方法可以看作是命令角色,当然,游戏内部具体是怎么实现的我们不知道,也不在这里探讨,仅作例子分析,最后真正执行处理具体逻辑的则是游戏本身,你可以看作是各种机器码计算处理来执行的具体逻辑,这里我们将它看作为接受者角色。逻辑分析比较清楚了,我们来将其“翻译”成代码,首先是我们的接收者,这里以俄罗斯方块游戏本身作为接受者角色。

        接受者角色,俄罗斯方块游戏。

/*** 接受者角色 俄罗斯方块游戏*/
public class TetrisMachine {/*** 真正处理“向左”操作的逻辑代码*/public void toLeft() {System.out.println("向左");}/*** 正在处理“向右”操作的逻辑代码*/public void toEight() {System.out.println("向右");}/*** 正常处理“快速下落”操作的逻辑代码*/public void fastToBottom() {System.out.println("快速下落");}/*** 真正处理“改变形状”操作的逻辑代码*/public void transform() {System.out.println("改变形状");}
}

        TetrisMachine 类整个命令模式中唯一处理具体代码逻辑的地方,其他的类都是直接或间接地调用到该来的方法,这就是接收者角色,处理具体的逻辑。如上文我们所说,接收者类只是一个普通的类,任何类都可以作为接收者。接下来我们定义一个接口作为命令角色的抽象。

        命令者抽象,定义执行方法。

/*** 命令者抽象 定义执行方法*/
public interface Command {/*** 命令执行*/void execute();
}

        然后就是4个具体命令:向左移、向右移、掉落和变换。

        具体命令,向左移的命令类。

/*** 具体命令 向左移的命令类*/
public class LeftCommand implements Command {//持有一个接收者俄罗斯方块游戏对象的引用private TetrisMachine machine;public LeftCommand(TetrisMachine machine) {this.machine = machine;}@Overridepublic void execute() {//调用游戏机里的具体方法执行操作machine.toLeft();}
}

        具体命令者,向右移的命令类。

/*** 具体命令者 向右移的命令类*/
public class RigthCommand implements Command {//持有一个接收者俄罗斯方块游戏对象的引用private TetrisMachine machine;public RigthCommand(TetrisMachine machine) {this.machine = machine;}@Overridepublic void execute() {//调用游戏机里的具体方法执行操作machine.toRight();}
}

        具体命令者,快速落下的命令类。

/*** 具体命令者,快速落下的命令类。*/
public class FallCommand implements Command {//持有一个接收者俄罗斯方块游戏对象的引用private TetrisMachine machine;public FallCommand(TetrisMachine machine) {this.machine = machine;}@Overridepublic void execute() {//调用游戏机里的具体方法执行操作machine.fastToBottom();}
}

        具体命令者,改变形状的命令类。

/*** 具体命令者,改变形状的命令类。*/
public class TransformCommand implements Command {//持有一个接收者俄罗斯方块游戏对象的引用private TetrisMachine machine;public TransformCommand(TetrisMachine machine) {this.machine = machine;}@Overridepublic void execute() {//调用游戏机里的具体方法执行操作machine.transform();}
}

        从程序中可以看到,命令者角色类中的方法名称与 TetrisMachine 接受者角色类中的方法名可以不一样,两者之间仅是一种弱耦合。对于请求者,这里我们以一个 Buttons 类来表示,命令由按钮来执行。

        请求者类,命名由按钮发起。

/*** 请求者类,命名由按钮发起。*/
public class Buttons {private LeftCommand leftCommand;//向左移动的命令对象引用private RightCommand rightCommand;//向右移动的命令对象引用private FallCommand fallCommand;//快速落下的命令对象引用private TransformCommand transformCommand;//变换形状的命令对象引用/*** 设置向左移动的命令对象** @param leftCommand 向左移动的命令对象*/public void setLeftCommand(LeftCommand leftCommand) {this.leftCommand = leftCommand;}/*** 设置向右移动的命令对象** @param rightCommand 向右移动的命令对象*/public void setRightCommand(RightCommand rightCommand) {this.rightCommand = rightCommand;}/*** 设置快速下落的命令对象** @param fallCommand 快速下落的命令对象*/public void setFallCommand(FallCommand fallCommand) {this.fallCommand = fallCommand;}/*** 设置变换形状的命令对象** @param transformCommand 变换形状的命令对象*/public void setTransformCommand(TransformCommand transformCommand) {this.transformCommand = transformCommand;}/*** 按下按钮向左移动*/public void toLeft() {leftCommand.execute();}/*** 按下按钮向右移动*/public void toRight() {rightCommand.execute();}/*** 按下按钮快速下落*/public void fall() {fallCommand.execute();}/*** 按下按钮改变状态*/public void transform() {transformCommand.execute();}
}

        客户类

/*** 客户类*/
public class Player {public static void main(String[] args) {//首先要有俄罗斯方块游戏TetrisMachine machine = new TetrisMachine();//根据游戏我们构造4种命令LeftCommand leftCommand = new LeftCommand(machine);RightCommand rightCommand = new RightCommand(machine);FallCommand fallCommand = new FallCommand(machine);TransformCommand transformCommand = new TransformCommand(machine);//按钮可以执行不同的命令Buttons buttons = new Buttons();buttons.setLeftCommand(leftCommand);buttons.setRightCommand(rightCommand);buttons.setFallCommand(fallCommand);buttons.setTransformCommand(transformCommand);//具体按下哪个按钮玩具说了算buttons.toLeft();buttons.toRight();buttons.fall();buttons.transform();}
}

        或许大家在看了这一长篇代码后心存疑虑,明明就是一个简单的逻辑,为什么要做的如此复杂呢?对于大部分开发者来说更愿意解释如下的代码。

/*** 客户类*/
public class Player {public static void main(String[] args) {//首先要有俄罗斯方块游戏TetrisMachine machine = new TetrisMachine();//要实现怎样的控制方式,我们就直接调用相关的函数就行machine.toLeft();machine.toRight();machine.fastToBottom();machine.transform();}
}

        调用逻辑做得如此复杂,这是因为来发起来方便,每次我们增加或许修改游戏功能只需修改 

TetrisMachine 类就行了,然后对应地改一改 Player 类,一切都很方便。但是,对开发者自己来说是方便了,那么,如果有一天开发者不再负责这个项目了呢?这样的逻辑留给后来者,没人觉得会方便。实际模式有一条重要的原则;对修改关闭对扩展开发,大家可以细细体会。

        除此之外,使用命令模式的另一个好处是可以实现命令记录功能,如在上例中,我们在请求者 Buttons 里使用一个数据结构来存储执行过的命令对象,以此可以方便地知道刚刚执行过哪些命令动作,并可以在需要时恢复,具体代码大家可自行尝试,这里不再给出。

在命令模式中,其充分体现了几乎所有设计模块的通病,就是类的膨胀,大量衍生类的创建,这是一个不可避免的问题,但是,其给我们带来的好处也非常多,更弱的耦合性、灵活性的控制性以及更好的扩展性,不过,在实际开发过程中是不是需要采用命令模式还是需要斟酌。

相关文章:

命令模式——让程序舒畅执行

● 命令模式介绍 命令模式(Command Pattern),是行为型设计模式之一。命令模式相对于其他的设计模式来说并没有那么多条条框框,其实并不是一个很“规矩”的模式,不过,就是基于一点,命令模式相对于…...

GZ035 5G组网与运维赛题第3套

2023年全国职业院校技能大赛 GZ035 5G组网与运维赛项(高职组) 赛题第3套 一、竞赛须知 1.竞赛内容分布 竞赛模块1--5G公共网络规划部署与开通(35分) 子任务1:5G公共网络部署与调试(15分) 子…...

071:mapboxGL上传含shp的zip文件,在map上解析显示图形

第071个 点击查看专栏目录 本示例是介绍演示如何在vue+mapbox中上传含有shp文件的zip,在地图上显示图形。这里先通过上传解压解析,转换生成geojson文件,然后在地图上渲染图形。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果所用的zip文…...

python下拉框选择测试

把下拉选择的值得打印出来: import tkinter as tk def on_select(event): # 当选择下拉框中的一项时,此函数将被调用 selected event.widget.cget("text") # 获取选中的文本 print(f"You selected: {selected}") # 打印选中…...

即时编译器JIT

类编译加载执行过程 如下图所示,一个Java代码从编译到运行大抵会经历以下几个过程。具体每个过程笔者会在下文站展开讨论。 类编译 首先是类编译阶段,这个阶段会将Java文件变为class文件,这个class文件包含一个常量池和方法表集合&#xf…...

npm更新包时This operation requires a one-time password.

[访问我的npm包](mhfwork/yt-ui - npm) 更新npm包时出现 This operation requires a one-time password.是因为需要认证 解决办法 1. 点击红线处的链接 2. 进入npm官网获取指定秘钥 3. 再次填入 one-time password 即可...

C++类模板再学习

之前已经学习了C类模板;类模板的写法和一般类的写法有很大的差别;不容易熟悉;下面再做一遍; 做一个椭圆类,成员有长轴长度和短轴长度; // ellipse.h: interface for the ellipse class. // //#if !define…...

华为终端智能家居应用方案

PLC-IoT概述 华为智能PLC-IoT工业物联网系列通信模块是基于电力线宽带载波技术的产品,实现数据在电力线上双向、高速、稳定的传输,广泛适用于电力、交通、工业制造、智能家居等领域,PLC-IoT通信模块包含头端和尾端两种类型,头端配…...

PHP下载文件

/***文件下载*param $filepath源文件路径 */function dwon_file($filepath){if(file_exists($filepath)){header(content-type:text/html;charsetutf8);header(Content-Description: File Transfer);header(Content-Type: application/octet-stream);header(Content-Dispositio…...

38基于matlab的期货预测,利用PSO优化SVM和未优化的SVM进行对比,得到实际输出和期望输出结果。

基于matlab的期货预测,利用PSO优化SVM和未优化的SVM进行对比,得到实际输出和期望输出结果。线性核函数、多项式、RBF核函数三种核函数任意可选,并给出均方根误差,相对误差等结果,程序已调通,可直接运行。 3…...

【Codeforces】 CF582D Number of Binominal Coefficients

题目链接 CF方向 Luogu方向 题目解法 看到 p α ∣ ( n k ) p^{\alpha} | \binom{n}{k} pα∣(kn​) ,首先想到 k u m m e r kummer kummer 定理,那么限制即为 n − k n-k n−k 和 k k k 做加法在 p p p 进制下的进位数 ≥ α \ge \alpha ≥α …...

sql第二次上机作业

1查找借阅了ISBN为“4-6045-1023-4”的借书证号,读者姓名,专业名和借书时间 use tsgl go select Reader.Lno,Rname,Spec,Lend.Bordate FROM Reader,Lend WHERE Reader.LnoLend.Lno AND ISBN 4-6045-1023-42查找借阅了《数据库原理》一书的借阅信息&…...

辅助驾驶功能开发-功能规范篇(22)-3-L2级辅助驾驶方案功能规范

1.3.3 TLA系统功能定义 1.3.3.1 状态机 1.3.3.2 状态迁移图 1.3.3.3 功能定义 1.3.3.3.1 信号需求列表 1.3.3.3.2 系统开启关闭 1)初始化 车辆上电后,交通灯辅助系统(TLA)进行初始化,控制器需在 220ms 内发出第一帧报文,并在 3s 内完成内部自检,同时上电 3s 内不进行…...

Python基础入门例程16-NP16 发送offer(列表)

目录 描述 输入描述: 输出描述: 解答 : 说明: 描述 某公司在面试结束后,创建了一个依次包含字符串 Allen 和 Tom 的列表offer_list,作为通过面试的名单。 请你依次对列表中的名字发送类似 Allen, you…...

Web前端面试之Vue—对Vue的理解

目录 一、web发展历程 二、vue是什么 三、Vue核心特性 组件化 数据驱动 指令 四、Vue与Angular以及React的区别 一、web发展历程 Web是World Wide Web的简称,中文译为万维网 我们可以将它规划成如下的几个时代来进行理解 静态网页:最早的网页是没…...

C/C++晶晶赴约会 2020年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C晶晶赴约会 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C晶晶赴约会 2020年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 晶晶的朋友贝贝约晶晶下周一起去看展览&#xff0…...

js 解决 H 指数

给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发…...

在JS中,var 、let 、const 总结

let是英文单词"let"的缩写。在JavaScript中,let 关键字用来声明一个块级作用域 的变量,这意味着变量仅在声明它的代码块内有效,超出该代码块作用域时就无法访问该变量。与var不同的是,let不会被提升到函数作用域或全局作…...

关于网络安全运营工作与安全建设工作的一些思考

以下内容是个人成长过程中对于网络安全运营工作的理解和思考,希望通过这篇文章帮助大家更好的去做安全运营体系化建设,开始吧! 文章目录 一、网络安全运营是什么?二、网络安全运营建设阶段第一阶段:设备限制阶段第二阶…...

【机器学习可解释性】4.SHAP 值

机器学习可解释性 1.模型洞察的价值2.特征重要性排列3.部分依赖图4.SHAP 值5.SHAP 值 高级使用 正文 理解各自特征的预测结果? 介绍 您已经看到(并使用)了从机器学习模型中提取一般解释技术。但是,如果你想要打破模型对单个预测的工作原理? SHAP 值…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

dify打造数据可视化图表

一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

消息队列系统设计与实践全解析

文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...