【HeadFirst系列之HeadFirst设计模式】第7天之命令模式:封装请求,轻松实现解耦!
命令模式:封装请求,轻松实现解耦!
大家好!今天我们来聊聊设计模式中的命令模式(Command Pattern)。如果你曾经需要将请求封装成对象,或者希望实现请求的撤销、重做等功能,那么命令模式就是你的不二之选!本文基于《Head First 设计模式》的命令模式章节,通过生动的故事和 Java 代码示例,带你轻松掌握命令模式的精髓。
1. 命令模式是什么?
命令模式是一种行为型设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、记录日志、撤销等操作。命令模式的核心思想是解耦请求的发送者和接收者,使得系统更加灵活和可扩展。
适用场景
- 需要将请求封装成对象,以便在不同的上下文中使用。
- 需要支持请求的撤销、重做、排队等功能。
- 需要解耦请求的发送者和接收者。
2. 命令模式的实现
故事背景
小明开发了一个智能家居系统,系统中有一个遥控器(RemoteControl)类,用于控制各种家电设备,比如灯(Light)、风扇(Fan)等。每个设备都有不同的操作,比如打开、关闭、调节亮度等。
问题出现
如果直接在遥控器中调用设备的方法,会导致遥控器和设备之间的耦合度过高。此外,如果需要支持撤销操作,代码会变得非常复杂。
解决方案:命令模式
小明决定使用命令模式,将每个操作封装成一个命令对象,从而解耦遥控器和设备。
代码实现
1. 定义命令接口
// 命令接口
interface Command {void execute();void undo();
}
2. 实现具体命令
// 具体命令:打开灯
class LightOnCommand implements Command {private Light light;public LightOnCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.on();}@Overridepublic void undo() {light.off();}
}// 具体命令:关闭灯
class LightOffCommand implements Command {private Light light;public LightOffCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.off();}@Overridepublic void undo() {light.on();}
}// 具体命令:打开风扇
class FanOnCommand implements Command {private Fan fan;public FanOnCommand(Fan fan) {this.fan = fan;}@Overridepublic void execute() {fan.on();}@Overridepublic void undo() {fan.off();}
}// 具体命令:关闭风扇
class FanOffCommand implements Command {private Fan fan;public FanOffCommand(Fan fan) {this.fan = fan;}@Overridepublic void execute() {fan.off();}@Overridepublic void undo() {fan.on();}
}
3. 定义设备类
// 灯类
class Light {public void on() {System.out.println("Light is on");}public void off() {System.out.println("Light is off");}
}// 风扇类
class Fan {public void on() {System.out.println("Fan is on");}public void off() {System.out.println("Fan is off");}
}
4. 实现遥控器
// 遥控器类
class RemoteControl {private Command[] onCommands;private Command[] offCommands;private Command undoCommand;public RemoteControl() {onCommands = new Command[2];offCommands = new Command[2];Command noCommand = new NoCommand();for (int i = 0; i < 2; i++) {onCommands[i] = noCommand;offCommands[i] = noCommand;}undoCommand = noCommand;}public void setCommand(int slot, Command onCommand, Command offCommand) {onCommands[slot] = onCommand;offCommands[slot] = offCommand;}public void onButtonWasPushed(int slot) {onCommands[slot].execute();undoCommand = onCommands[slot];}public void offButtonWasPushed(int slot) {offCommands[slot].execute();undoCommand = offCommands[slot];}public void undoButtonWasPushed() {undoCommand.undo();}
}// 空命令类
class NoCommand implements Command {@Overridepublic void execute() {System.out.println("No command assigned");}@Overridepublic void undo() {System.out.println("No command assigned");}
}
5. 客户端代码
public class SmartHomeApp {public static void main(String[] args) {// 创建设备Light livingRoomLight = new Light();Fan livingRoomFan = new Fan();// 创建命令Command lightOn = new LightOnCommand(livingRoomLight);Command lightOff = new LightOffCommand(livingRoomLight);Command fanOn = new FanOnCommand(livingRoomFan);Command fanOff = new FanOffCommand(livingRoomFan);// 创建遥控器RemoteControl remoteControl = new RemoteControl();remoteControl.setCommand(0, lightOn, lightOff);remoteControl.setCommand(1, fanOn, fanOff);// 操作遥控器remoteControl.onButtonWasPushed(0); // 输出: Light is onremoteControl.offButtonWasPushed(0); // 输出: Light is offremoteControl.undoButtonWasPushed(); // 输出: Light is onremoteControl.onButtonWasPushed(1); // 输出: Fan is onremoteControl.offButtonWasPushed(1); // 输出: Fan is offremoteControl.undoButtonWasPushed(); // 输出: Fan is on}
}
3. 命令模式的优点
-
解耦请求的发送者和接收者
命令模式将请求封装成对象,使得请求的发送者和接收者之间没有直接的依赖关系。 -
支持撤销和重做
通过实现undo()
方法,可以轻松实现撤销操作。 -
支持请求的排队和日志记录
命令对象可以被存储、传递和记录,从而支持请求的排队和日志记录。 -
易于扩展
新增命令时,只需实现新的命令类,无需修改现有代码。
4. 总结
命令模式通过将请求封装成对象,实现了请求的发送者和接收者之间的解耦,从而使得系统更加灵活和可扩展。通过本文的讲解和代码示例,相信你已经掌握了命令模式的核心思想和实现方法。在实际开发中,命令模式非常适合用于实现撤销、重做、排队等功能。
互动话题
你在项目中用过命令模式吗?遇到过哪些问题?欢迎在评论区分享你的经验!
相关文章:

【HeadFirst系列之HeadFirst设计模式】第7天之命令模式:封装请求,轻松实现解耦!
命令模式:封装请求,轻松实现解耦! 大家好!今天我们来聊聊设计模式中的命令模式(Command Pattern)。如果你曾经需要将请求封装成对象,或者希望实现请求的撤销、重做等功能,那么命令模…...
HTTPS(下)
主要讲加密算法RSA,ECDHE TLS的握手涉及四次通信,根据不同的密钥交换算法,TLS 握手流程也会不一样的,现在常用的密钥交换算法有两种:RSA 算法和 ECDHE 算法 正常情况下,需要先TCP三次握手后进行TLS四次握手…...
vue2 和 vue3 中 computer 计算属性的用法
Vue 2 中的 computed 在 Vue 2 中,计算属性是响应式的,并且基于 getter 进行缓存,只有依赖的响应式数据发生变化时才会重新计算。 基本用法 <template><div><p>原始消息:{{ message }}</p><p>反…...

【STM32学习】标准库实现STM32 ADC采集1路、2路、多路
目录 ADC采集 ADC配置步骤 STM32F103C8T6的ADC 输入通道 编辑 1路ADC(A4 ADC 通道4) 1路ADC源码代码链接: 2路ADC(A4 ADC 通道4、A5 ADC 通道5)基于DMA实现 多路ADC实现采集 ADC采集 ADC配置步骤 使能GPIO…...

【STM32】外部时钟|红外反射光电开关
1.外部时钟 单片机如何对外部触发进行计数?先看一下内部时钟,内部时钟是接在APB1和APB2时钟线上的,APB1,APB2来自stm32单片机内部的脉冲信号,也叫内部时钟。我们用来定时。同样我们可以把外部的信号接入单片机,来对其…...
【语音科学计算器】当前汇率
JSON_MARKER_HORN{“base”:“USD”,“rates”:{“EUR”:0.9758,“JPY”:157.68,“GBP”:0.8190,“CNY”:7.3327,“HKD”:7.7872,“AUD”:1.6260,“CAD”:1.4422,“CHF”:0.9157,“SGD”:1.3714,“KRW”:1473.05,“NZD”:1.7992,“THB”:34.54,“MYR”:4.4930,“PHP”:57.32,“…...

PHP post 数据丢失问题
max_input_vars是PHP配置选项之一,用于设置一个请求中允许的最大输入变量数。它指定了在处理POST请求或者通过URL传递的参数时,PHP脚本能够接收和处理的最大变量数量。 max_input_vars的默认值是1000,意味着一个请求中最多可以包含1000个输入…...
【云服务器】云服务器内存不够用,开启SWAP交换分区
交换分区(Swap) 1.创建 2GB Swap 文件 sudo fallocate -l 2G /swapfile (如果 fallocate 不支持,可以用 dd 命令) sudo dd if/dev/zero of/swapfile bs1M count2048 2.设置 Swap 权限 sudo chmod 600 /swapfile…...
未来SLAM的研究方向和热点
SLAM(Simultaneous Localization and Mapping)是同时定位与地图构建的缩写,指的是机器人或设备在一个未知环境中一边进行自我定位,一边构建出环境的地图。SLAM广泛应用于机器人、自动驾驶、无人机等领域,涉及多个研究方…...

Orange 单体架构 - 快速启动
1 后端服务 1.1 基础设施 组件说明版本MySQLMySQL数据库服务5.7/8JavaJava17redis-stackRedis向量数据库最新版本Node安装Node22.11.0 1.2 orange-dependencies-parent 项目Maven依赖版本管理 1.2.1 项目克隆 GitHub git clone https://github.com/hengzq/orange-depende…...
【SQL】多表查询案例
📢本章节主要学习使用SQL多表查询的案例,多表查询基础概念 请点击此处。 🎄数据准备 首先我们创建一个新的表也就是薪资等级表,其余两个表(员工表和薪资表)在多表查询章节中已经创建。然后我么根据这三个表完成下面的12个需求。 create tab…...

springboot系列十四: 注入Servlet, Filter, Listener + 内置Tomcat配置和切换 + 数据库操作
文章目录 注入Servlet, Filter, Listener官方文档基本介绍使用注解方式注入使用RegistrationBean方法注入DispatcherServlet详解 内置Tomcat配置和切换基本介绍内置Tomcat配置通过application.yml完成配置通过类配置 切换Undertow 数据库操作 JdbcHikariDataSource需求分析应用…...
力扣-贪心-53 最大子数组和
思路 先把每一个值都加到当前集合中,记录当前的和,直到当前记录和小于0了,再重置改记录,再次尝试累加 代码 class Solution { public:int maxSubArray(vector<int>& nums) {int res INT32_MIN;int curSum 0;for(in…...
吃一堑长一智
工作中经历,有感触记录下 故事一 以前在一家公司时,自己是一名开发人员,遇到问题请教领导解决方案,当时领导给了建议,后来上线后出问题了,背了锅。心里想的是领导说这样做的呀,为什么出问题还…...

aws(学习笔记第二十九课) aws cloudfront hands on
aws(学习笔记第二十九课) 使用aws cloudfront 学习内容: 什么是aws cloudfront练习使用aws cloudfront 1. 什么是aws cloudfront aws cloudfront的整体架构 这里可以看出,aws引入了edge location的概念,用户的client与edge location进行是…...

deepseek自动化代码生成
使用流程 效果第一步:注册生成各种大模型的API第二步:注册成功后生成API第三步:下载vscode在vscode中下载agent,这里推荐使用cline 第四步:安装完成后,设置模型信息第一步选择API provider: Ope…...
【C++八股】内存对⻬
内存对齐是指编译器按照特定规则安排数据在内存中的存储位置,以提高程序的执行效率和可移植性。 内存对齐的原因: 1. 性能优化: 现代处理器通常要求数据在内存中按照特定的边界对齐,以提高内存访问效率。 如果数据未对齐&#x…...

idea连接gitee后.反向创建仓库和分支
文章目录 自动关联你登录的账号填写你的仓库和分支完成后会在gitee上创建一个仓库 (使用idea远程兼容gitee并反向创建仓库和分支) 自动关联你登录的账号 填写你的仓库和分支 完成后会在gitee上创建一个仓库...
汽车自动驾驶辅助L2++是什么?
自动驾驶辅助级别有哪些? 依照SAE(SAE International,Society of Automotive Engineers国际自动机工程师学会)的标准,大致划分为6级(L0-L5): L0人工驾驶:即没有驾驶辅助…...

围棋打谱应用软件设计制作
围棋打谱应用软件设计制作 五子棋游戏是大家耳熟能详的游戏,深受大众喜爱。可见其在智能游戏中的地位。我在本站发了好几篇文章介绍编制方法和算法。而类似的围棋游戏则是智能游戏的顶级存在。今在此基础上编制一款围棋打谱软件。当然这是简单的游戏程序࿰…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...