news 2026/4/23 16:49:15

设计模式学习(16) 23-14 命令模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式学习(16) 23-14 命令模式

文章目录

  • 0. 个人感悟
  • 1. 概念
  • 2. 适配场景
    • 2.1 适合的场景
    • 2.2 常见场景举例
  • 3. 实现方法
    • 3.1 实现思路
    • 3.2 UML类图
    • 3.3 代码示例
  • 4. 优缺点
    • 4.1 优点
    • 4.2 缺点
  • 5. 源码分析

0. 个人感悟

  • 命令模式核心是将请求或者操作封装成对象。那么就可以基于这些对象进行额外操作,比如队列记录、日志、撤销恢复等
  • 实际工作中,对于任务队列其实已经有很多成熟的框架,不过万变不离其宗,理解命令模式,对于其它知识(比如三方件、架构)的学习还是很有帮助的

1. 概念

英文定义(《设计模式:可复用面向对象软件的基础》)

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or long requests, and support undoable operations.

中文翻译

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

理解

  1. 请求封装:将"做什么"(操作)和"谁来做"(执行者)分离
  2. 参数化:可以像传递参数一样传递命令对象
  3. 延迟执行:命令可以在创建后的某个时间点执行
  4. 可撤销/重做:通过记录命令历史实现操作回退

2. 适配场景

2.1 适合的场景

  1. 解耦调用者和接收者:需要将请求的发起者和执行者解耦时
  2. 支持撤销/重做:需要实现操作的撤销和重做功能
  3. 任务队列/日志:需要将请求排队、记录日志或延迟执行

2.2 常见场景举例

  • 遥控器控制家电:不同按钮触发不同设备的不同操作
  • 餐厅点餐系统:订单作为命令,厨师作为接收者
  • 文本编辑器:撤销/重做功能
  • 线程池任务调度:将任务封装为命令对象
  • 游戏控制:玩家输入映射到游戏角色的不同动作

3. 实现方法

3.1 实现思路

  1. 定义命令接口:声明执行命令的抽象方法(通常包含execute()undo()
  2. 创建具体命令类:实现命令接口,关联接收者对象
  3. 定义接收者类:实际执行操作的对象
  4. 创建调用者/请求者类:持有命令对象并触发执行
  5. 客户端组装:创建命令对象并设置给调用者

3.2 UML类图

![[命令模式_UML.png]]

角色说明

  • Command(命令接口):声明执行操作的接口
  • ConcreteCommand(具体命令):绑定接收者和动作
  • Receiver(接收者):知道如何执行请求的具体操作
  • Invoker(调用者):持有命令对象并触发执行
  • Client(客户端):创建具体命令并设置接收者

3.3 代码示例

背景: 遥控器控制家电,可以遥控灯、电视灯家具
命令接口:

publicinterfaceCommand{/** * @description 执行 * @author bigHao * @date 2026/1/20 **/voidexecute();/** * @description 撤销 * @author bigHao * @date 2026/1/20 **/voidundo();}

灯的接受者和具体命令:

publicclassLightReceiver{/** * @description 开灯 * @author bigHao * @date 2026/1/20 **/publicvoidon(){System.out.println("开灯了");}/** * @description 关灯 * @author bigHao * @date 2026/1/20 **/publicvoidoff(){System.out.println("关灯了");}}publicclassLightOnCommandimplementsCommand{privateLightReceiverlightReceiver;publicLightOnCommand(LightReceiverlightReceiver){this.lightReceiver=lightReceiver;}@Overridepublicvoidexecute(){lightReceiver.on();}@Overridepublicvoidundo(){lightReceiver.off();}}publicclassLightOffCommandimplementsCommand{privateLightReceiverlightReceiver;publicLightOffCommand(LightReceiverlightReceiver){this.lightReceiver=lightReceiver;}@Overridepublicvoidexecute(){lightReceiver.off();}@Overridepublicvoidundo(){lightReceiver.on();}}

电视接受者和命令:

publicclassTVReceiver{/** * @description 开机 * @author bigHao * @date 2026/1/20 **/publicvoidon(){System.out.println("电视开了");}/** * @description 关机 * @author bigHao * @date 2026/1/20 **/publicvoidoff(){System.out.println("电视关了");}}publicclassTVOnCommandimplementsCommand{privateTVReceiverreceiver;publicTVOnCommand(TVReceiverreceiver){this.receiver=receiver;}@Overridepublicvoidexecute(){receiver.on();}@Overridepublicvoidundo(){receiver.off();}}publicclassTVOffCommandimplementsCommand{privateTVReceiverreceiver;publicTVOffCommand(TVReceiverreceiver){this.receiver=receiver;}@Overridepublicvoidexecute(){receiver.off();}@Overridepublicvoidundo(){receiver.on();}}

遥控器:

publicclassRemoteController{publicstaticfinalintINIT_COMMAND_NUM=5;privateCommand[]onCommands;privateCommand[]offCommands;privateCommandundoCommand;publicRemoteController(){onCommands=newCommand[INIT_COMMAND_NUM];offCommands=newCommand[INIT_COMMAND_NUM];for(inti=0;i<INIT_COMMAND_NUM;i++){onCommands[i]=newNoCommand();offCommands[i]=newNoCommand();}}publicvoidsetOnCommand(intno,CommandonCommand,CommandoffCommand){onCommands[no]=onCommand;offCommands[no]=offCommand;}publicvoidon(intno){onCommands[no].execute();// 记录当前操作undoCommand=onCommands[no];}publicvoidoff(intno){offCommands[no].execute();// 记录当前操作undoCommand=offCommands[no];}publicvoidundo(){undoCommand.undo();}}

测试:

publicclassClient{publicstaticfinalintLIGHT_NO=0;publicstaticfinalintTV_NO=1;staticvoidmain(){RemoteControllerremoteController=newRemoteController();LightReceiverlightReceiver=newLightReceiver();LightOnCommandlightOnCommand=newLightOnCommand(lightReceiver);LightOffCommandlightOffCommand=newLightOffCommand(lightReceiver);// 按键0是灯开关remoteController.setOnCommand(LIGHT_NO,lightOnCommand,lightOffCommand);System.out.println("=== 按下开灯键位 ===");remoteController.on(LIGHT_NO);System.out.println("=== 按下关灯键位 ===");remoteController.off(LIGHT_NO);System.out.println("=== 按下撤销键 ===");remoteController.undo();TVReceivertvReceiver=newTVReceiver();TVOnCommandtvOnCommand=newTVOnCommand(tvReceiver);TVOffCommandtvOffCommand=newTVOffCommand(tvReceiver);// 按键1是灯开关remoteController.setOnCommand(TV_NO,tvOnCommand,tvOffCommand);System.out.println("=== 按下开机键位 ===");remoteController.on(TV_NO);System.out.println("=== 按下关机键位 ===");remoteController.off(TV_NO);System.out.println("=== 按下撤销键 ===");remoteController.undo();}}

输出:

=== 按下开灯键位 === 开灯了 === 按下关灯键位 === 关灯了 === 按下撤销键 === 开灯了 === 按下开机键位 === 电视开了 === 按下关机键位 === 电视关了 === 按下撤销键 === 电视开了

4. 优缺点

4.1 优点

高内聚低耦合

  • 调用者与接收者解耦:调用者无需知道接收者的具体实现
  • 命令对象内聚性高:每个命令专注于一个具体操作

复用性与可扩展性

  • 易于扩展新命令:只需实现Command接口
  • 命令可复用:同一命令可在不同上下文中使用

维护性

  • 易于维护和修改:修改具体操作只需修改对应命令类
  • 支持宏命令:可将多个命令组合成复杂操作

稳定性与可靠性

  • 支持事务:可批量执行命令并支持回滚
  • 支持撤销/重做:通过命令历史记录实现

4.2 缺点

复杂性增加

  • 类数量增多:每个命令都需要一个具体类
  • 系统复杂度提高:增加了间接层次

性能开销

  • 内存占用:每个命令都需要创建对象
  • 执行效率:间接调用可能比直接调用稍慢

5. 源码分析

Java标准库中Runnable相关实现是简化的命令模式
java.lang.Runnable接口

// Runnable就是命令接口publicinterfaceRunnable{publicabstractvoidrun();// execute()方法}// Thread作为InvokerThreadthread=newThread(()->System.out.println("Running command"));thread.start();// 触发命令执行

角色分析:

  1. Command(命令接口):Runnable接口,它定义了run()方法,相当于命令模式中的执行方法。
  2. ConcreteCommand(具体命令):实现了Runnable接口的类,例如我们通过匿名内部类、Lambda表达式或者具体类实现的run方法中的具体逻辑。
  3. Receiver(接收者):可以是Ru实际执行操作的对象。通常Runnable的实现会调用其他对象(接收者)的方法。
  4. Invoker(调用者/请求者):调用命令的对象。在Java中,Thread类就是一个典型的调用者,它接收一个Runnable(命令)并在适当的时机调用其run方法。

参考:

  • 韩顺平 Java设计模式
  • 张维鹏 Java设计模式之行为型:命令模式
  • java_my_life《JAVA与模式》之命令模式
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 11:29:25

Agentic RAG核心解析(必收藏):从原理到架构,搞定复杂场景检索

Agentic RAG&#xff08;智能体增强检索增强生成&#xff09;是将Agent自主规划与决策能力融入传统RAG的进阶技术&#xff0c;核心目标是突破传统RAG的局限&#xff0c;高效应对企业级复杂查询场景。相较于传统RAG的固定流程&#xff0c;它能自主选择检索引擎、规划检索步骤、评…

作者头像 李华
网站建设 2026/4/10 3:54:30

基于SpringBoot的校园生活服务平台毕业设计源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于SpringBoot框架的校园生活服务平台&#xff0c;以实现以下研究目的&#xff1a; 首先&#xff0c;通过构建校园生活服务平台&#xff0c;…

作者头像 李华
网站建设 2026/4/23 11:47:13

DarkHole

信息收集查看靶机IParp-scan -lnmap -O 132.168.129.0/24 --min-rate 1000得到目标靶机IP为192.168.129.138查看开放的端口&#xff0c;有22、80端口目录扫描dirsearch -u "192.168.129.138"dirb http://192.168.129.138有注册登录界面&#xff0c;upload界面&#x…

作者头像 李华
网站建设 2026/4/23 11:49:07

模组的功耗说明

一&#xff0c;模组的低功耗特性概述二、三种不同的功耗模式1&#xff0c; 三种功耗模式的简捷定义模组有三种功耗模式&#xff0c; 分别是&#xff1a; 常规模式&#xff0c;低功耗模式&#xff0c;PSM模式&#xff0c;定义如下&#xff1a;- 1&#xff09;常规模式&#xff1…

作者头像 李华
网站建设 2026/4/23 11:45:47

不只是图片:深入理解 GIS 栅格数据本质与 GDAL 读写实战

GIS 栅格数据只是一张普通图片吗&#xff1f;其实它可以是高程、降雨量、土地类型&#xff0c;甚至是二维空间信号。本文节选自新书《GIS基础原理与技术实践》第5章&#xff0c;带你穿透表象&#xff0c;掌握栅格数据的本质与 GDAL 开发核心技能。第5章 地理空间数据之栅格 导言…

作者头像 李华
网站建设 2026/4/23 11:50:52

AI在英语口语学习中的应用

AI技术已经彻底告别了早期的“机械复读”模式&#xff0c;演变为具有情感、逻辑且能实时反馈的“数字外教”。对于英语口语学习者来说&#xff0c;AI的应用已经深入到从零基础纠音到高阶职场实战的全过程。以下是AI在英语口语学习中的五大核心应用&#xff1a;1. “音素级”实时…

作者头像 李华