设计模式——命令设计模式(行为型)
摘要
本文介绍了命令设计模式,这是一种行为型设计模式,用于将请求封装为对象,实现请求的解耦和灵活控制。它包含命令接口、具体命令、接收者、调用者和客户端等角色,优点是解耦请求发送者与接收者,支持命令的排队、记录、撤销等操作,但会增加系统复杂性。文中通过点餐系统类比说明其结构,并介绍了实现方式、适合场景和实战示例。
1. 命令设计模式定义
将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,队列或记录请求日志,以及支持可撤销的操作。命令模式将“命令的发起者”与“命令的执行者”解耦,通过封装命令对象来实现请求的解耦和灵活控制。
- 优点:解耦请求发送者与接收者、支持命令的排队、记录、撤销/重做、更容易扩展新命令类
- 缺点:增加了系统中类的数量,带来一定结构复杂性
1.1. 🧱 模式结构组成:
角色 | 说明 |
Command(命令接口) | 声明执行操作的接口。 |
ConcreteCommand(具体命令) | 实现命令接口,绑定接收者并调用其方法。 |
Receiver(接收者) | 执行命令实际操作的类。 |
Invoker(调用者) | 负责调用命令对象来执行请求。 |
Client(客户端) | 创建命令对象并将其配置给调用者。 |
1.2. ✅ 举例类比(生活化):
点餐系统中:
- 顾客 →
Invoker
- 服务员传菜 →
Command
- 厨师 →
Receiver
- 每道菜 →
ConcreteCommand
2. 命令设计模式结构
命令模式包含如下角色:
- Command: 抽象命令类
- ConcreteCommand: 具体命令类
- Invoker: 调用者
- Receiver: 接收者
- Client:客户类
2.1. 命令设计模式类图
2.2. 命令设计模式时序图
3. 命令设计模式实现方式
命令设计模式的实现方式,核心在于 将命令抽象成独立对象,使得发送者和接收者解耦。下面是标准实现结构和 Java 示例实现方式,结合注解风格以方便应用于 Spring 项目中。
3.1. 定义命令接口(Command)
public interface Command {void execute();
}
3.2. 创建接收者类(Receiver)
public class RiskDecisionReceiver {public void approve() {System.out.println("审批通过逻辑执行...");}public void reject() {System.out.println("审批拒绝逻辑执行...");}
}
3.3. 创建具体命令类(ConcreteCommand)
public class ApproveCommand implements Command {private final RiskDecisionReceiver receiver;public ApproveCommand(RiskDecisionReceiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {receiver.approve();}
}public class RejectCommand implements Command {private final RiskDecisionReceiver receiver;public RejectCommand(RiskDecisionReceiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {receiver.reject();}
}
3.4. 调用者类(Invoker)
public class DecisionInvoker {private Command command;public void setCommand(Command command) {this.command = command;}public void run() {command.execute();}
}
3.5. 客户端调用示例(Client)
public class RiskDecisionClient {public static void main(String[] args) {RiskDecisionReceiver receiver = new RiskDecisionReceiver();Command approveCommand = new ApproveCommand(receiver);Command rejectCommand = new RejectCommand(receiver);DecisionInvoker invoker = new DecisionInvoker();// 审批通过流程invoker.setCommand(approveCommand);invoker.run();// 审批拒绝流程invoker.setCommand(rejectCommand);invoker.run();}
}
3.6. Spring 实现提示(可选)
如果你使用 Spring,可以用如下方式注入和管理:
- 使用
@Component
管理Receiver
和ConcreteCommand
- 使用
@Autowired
注入Receiver
到命令中 - 用策略注册表 + 命令名映射管理命令
结果好处
- 增加新命令非常容易(只需实现接口)
- 接收者逻辑和命令发送者解耦
- 可以实现撤销、重做、命令日志等增强功能
4. 命令设计模式适合场景
4.1. ✅ 适合使用命令设计模式的场景
场景 | 说明 |
需要将请求调用者与接收者解耦 | 调用者只负责触发命令,不知道命令如何实现。 |
需要对请求排队、记录日志、支持撤销/重做 | 如编辑器中的操作撤销、银行交易日志等。 |
需要支持宏命令(多个命令组合执行) | 比如批量操作、流水线处理等。 |
需要将操作封装为对象传递 | 方便将命令存入队列、数据库,延迟执行。 |
系统中存在多个操作行为,需要灵活扩展 | 每种行为封装为命令,新增行为无需改调用者逻辑。 |
远程调用 / 宏任务调度 | 比如分布式系统中对远程服务的封装。 |
4.2. ❌ 不适合使用命令设计模式的场景
场景 | 原因 |
业务操作非常简单,没有扩展需求 | 引入命令对象反而让结构复杂,得不偿失。 |
命令对象数量极多且变化频繁 | 会导致命令类泛滥,维护成本变高。 |
不需要记录历史或撤销操作的业务 | 命令封装就失去了很多优势,增加冗余。 |
操作同步且不需要解耦调用者和执行者 | 可以直接调用接收者方法,无需封装为命令。 |
高性能敏感系统中 | 引入命令对象和调用链会带来额外性能开销。 |
4.3. 📌 命令设计模式的场景总结:
项目 | 命令模式适用 | 不适用 |
是否需要行为解耦 | ✅ 是 | ❌ 否 |
是否需要日志/撤销/排队 | ✅ 是 | ❌ 否 |
行为是否变化频繁 | ✅ 是 | ❌ 否(简单稳定) |
是否存在命令组合需求 | ✅ 是 | ❌ 否 |
是否为高频调用或性能敏感 | ❌ 否(不适用) | ✅ 是 |
5. 命令设计模式实战示例
5.1. 🌟 场景简介
风控命令中心根据业务场景动态执行不同的风控策略,如:
- 黑名单校验
- 信用评分校验
- 设备指纹校验等
5.2. 1️⃣ 命令接口定义
public interface RiskCommand {void execute(RiskContext context);
}
5.3. 2️⃣ 风控上下文对象(命令输入参数)
public class RiskContext {private String userId;private String ip;private String deviceId;private Map<String, Object> resultMap = new HashMap<>();// getter/setter省略
}
5.4. 3️⃣ 各种具体命令实现
5.4.1. 黑名单校验命令
@Component("blacklistCommand")
public class BlacklistCommand implements RiskCommand {@Overridepublic void execute(RiskContext context) {// 模拟黑名单校验逻辑System.out.println("执行黑名单校验 for user: " + context.getUserId());context.getResultMap().put("blacklist", false);}
}
5.4.2. 信用分数校验命令
@Component("creditScoreCommand")
public class CreditScoreCommand implements RiskCommand {@Overridepublic void execute(RiskContext context) {// 模拟信用分校验逻辑System.out.println("执行信用分校验 for user: " + context.getUserId());context.getResultMap().put("creditScorePass", true);}
}
5.5. 4️⃣ 命令调用者(Invoker)
@Component
public class RiskCommandInvoker {@Autowiredprivate ApplicationContext applicationContext;public void runCommands(List<String> commandNames, RiskContext context) {for (String name : commandNames) {RiskCommand command = applicationContext.getBean(name, RiskCommand.class);command.execute(context);}}
}
5.6. 5️⃣ 控制器或服务调用示例
@Service
public class RiskEngineService {@Autowiredprivate RiskCommandInvoker invoker;public void runRiskProcess(String userId) {RiskContext context = new RiskContext();context.setUserId(userId);context.setIp("192.168.1.1");context.setDeviceId("DEVICE123");List<String> commands = Arrays.asList("blacklistCommand", "creditScoreCommand");invoker.runCommands(commands, context);System.out.println("风控结果: " + context.getResultMap());}
}
5.7. ✅ 总结优势
点 | 说明 |
解耦 | 控制器无需知道风控规则细节 |
灵活扩展 | 添加新命令只需实现接口并加 |
动态组合 | 命令名可来自配置文件、数据库、用户输入等 |
日志/回滚 | 每个命令可独立记录日志或支持回滚逻辑 |
6. 命令设计模式思考
6.1. 命令模式是不是适合用于在Controller 层与service层之间?用于构建Controller 的命令?
6.1.1. 可以使用,但不是最佳常规选择
命令模式主要用于封装请求为对象,从而实现请求的参数化、请求排队、撤销、日志、事务等操作。在某些特定场景下可以作为Controller与Service之间的中间层抽象使用,例如:
6.1.2. ✅ 适用场景
场景 | 说明 |
控制层处理的业务请求种类多、变化大 | 使用命令模式封装请求参数及处理逻辑,使代码结构清晰 |
控制层希望将不同业务处理模块“延迟执行”或“排队执行” | 命令可异步队列执行 |
需要记录请求日志、支持撤销/回滚等扩展行为 | 命令对象天然适合这些行为 |
多个 Controller 共用相似的业务指令 | 可将通用处理抽象成命令复用 |
例如在风控、工作流、审批流等系统,Controller 接收请求后会根据参数生成命令对象,并交由统一的命令执行器处理逻辑,非常合适使用命令模式。
6.1.3. ❌ 不适合场景(常规应用)
对于大部分业务系统,如果 Controller 只是简单地调用 Service 的方法,并没有复杂的行为拆解需求:
- 命令模式反而会引入过多抽象;
- 增加类和代码复杂度;
- 开发维护成本上升。
6.1.4. ✳️ 总结判断标准
需求 | 是否适合命令模式 |
请求结构稳定、简单 | ❌ 不适合 |
业务逻辑复杂、执行方式灵活 | ✅ 适合 |
希望请求可记录、排队、组合、延迟执行 | ✅ 非常适合 |
控制层职责只做参数接收和转发 | ❌ 不适合 |
6.1.5. ✅ 示例(适合的 Controller 场景)
@RestController
public class RiskController {@Autowiredprivate RiskCommandInvoker invoker;@PostMapping("/risk/check")public ResponseEntity<String> riskCheck(@RequestBody RiskRequest request) {RiskContext context = new RiskContext();context.setUserId(request.getUserId());// 将 Controller 参数转化为一组命令List<String> commands = Arrays.asList("blacklistCommand", "creditScoreCommand");invoker.runCommands(commands, context);return ResponseEntity.ok("风控结果: " + context.getResultMap());}
}
博文参考
- 1. 命令模式 — Graphic Design Patterns
- 责任链设计模式(职责链模式)
相关文章:

设计模式——命令设计模式(行为型)
摘要 本文介绍了命令设计模式,这是一种行为型设计模式,用于将请求封装为对象,实现请求的解耦和灵活控制。它包含命令接口、具体命令、接收者、调用者和客户端等角色,优点是解耦请求发送者与接收者,支持命令的排队、记…...
鸿蒙OSUniApp智能商品展示实战:打造高性能的动态排序系统#三方框架 #Uniapp
UniApp智能商品展示实战:打造高性能的动态排序系统 引言 在电商应用开发中,商品展示和智能排序是提升用户体验的关键因素。随着HarmonyOS生态的发展,用户对应用的性能和交互体验要求越来越高。本文将深入探讨如何在UniApp中实现一个性能优异…...

03 APP 自动化-定位元素工具元素定位
文章目录 一、Appium常用元素定位工具1、U IAutomator View Android SDK 自带的定位工具2、Appium Desktop Inspector3、Weditor安装:Weditor工具的使用 4、uiautodev通过定位工具获取app页面元素有哪些属性 二、app 元素定位方法 一、Appium常用元素定位工具 1、U…...

PABD 2025:大数据与智慧城市管理的融合之道
会议简介 2025年公共管理与大数据国际会议(ICPMBD 2025)确实在海口举办。本次会议将围绕公共管理与大数据的深度融合、数据分析在公共管理中的应用、大数据驱动的政策制定与优化等议题展开深入研讨。参会者将有机会聆听前沿学术报告,分享研究…...

Golang持续集成与自动化测试和部署
概述 Golang是一门性能优异的静态类型语言,但因其奇快的编译速度,结合DevOps, 使得它也非常适合快速开发和迭代。 本文讲述如何使用Golang, 进行持续集成与自动化测试和部署。主要使用了以下相关技术: dep: 进行包的依赖管理gin…...
三套知识系统的实践比较:Notion、Confluence 与 Gitee Wiki
在过去几年中,我们团队先后使用过三套企业知识系统:Notion、Confluence 和 Gitee Wiki。每一套系统上线初期都带来一阵热情,但最终能真正融入研发流程、持续活跃的,只有最后一个。 我们不是要为某个平台背书,而是希望…...

mysql离线安装教程
1.下载地址: https://downloads.mysql.com/archives/community/ 2.上传安装包到系统目录,并解压 tar -xvf mysql-8.0.34-1.el7.x86_64.rpm-bundle.tar3.检查系统中是否存在mariadb的rpm包 rpm -qa|grep mariadb存在则删除 rpm -e xxx4.解压完后执行如下命令安装 sudo rpm -iv…...
OpenGL 3D 编程
OpenGL 是一个强大的跨平台图形 API,用于渲染 2D 和 3D 图形。以下是 OpenGL 3D 编程的入门基础。 一. 环境设置 安装必要的库 GLFW: 用于创建窗口和处理输入 GLEW 或 GLAD: 用于加载 OpenGL 函数 GLM: 数学库,用于 3D 变换 // 基本 OpenGL 程序结构示例 #include <GL/g…...

基于FPGA的VGA显示文字和动态数字基础例程,进而动态显示数据,类似温湿度等
基于FPGA的VGA显示文字和数字 前言一、VGA显示参数二、字模生成三、代码分析1.vga_char顶层2.vga_ctrl驱动文件3.vga_pic数据准备文件 总结 前言 结合正点原子以及野火的基础例程,理解了VGA本身基本协议,VGA本身显示像素为640*480,因此注意生…...

力扣刷题Day 68:搜索插入位置(35)
1.题目描述 2.思路 方法1:回溯的二分查找。 方法2:看到了一个佬很简洁的写法,代码贴在下面了。 3.代码(Python3) 方法1: class Solution:def searchInsert(self, nums: List[int], target: int) ->…...
NodeJS全栈WEB3面试题——P4Node.js后端集成 服务端设计
4.1 如何在 Node.js 中管理钱包与私钥的安全性? 私钥管理原则:不暴露,不硬编码,不明文存储。 常见做法: 加密存储: 使用 crypto 或 ethers.Wallet.encrypt() 加密私钥,存储到数据库或文件系统…...
SQL进阶之旅 Day 12:分组聚合与HAVING高效应用
【SQL进阶之旅 Day 12】分组聚合与HAVING高效应用 在SQL的世界里,分组聚合(Grouping and Aggregation)是处理大规模数据集时最常用的技术之一。它允许我们将数据按照某些列进行分类,并对每个分类进行统计计算。而 HAVING 子句则是…...
深入剖析C#构造函数执行:基类调用、初始化顺序与访问控制
导言 在面向对象编程中,理解对象构造过程至关重要。C#的构造函数执行遵循严格的顺序规则,尤其是涉及继承和成员初始化时。本文将深入解析构造函数的执行流程、初始化语句的妙用以及类访问修饰符的影响,助你写出更健壮、可维护的代码。 构造…...
Java 大数据处理:使用 Hadoop 和 Spark 进行大规模数据处理
Java 大数据处理:使用 Hadoop 和 Spark 进行大规模数据处理 在当今数字化时代,数据呈现出爆炸式增长,如何高效地处理大规模数据成为企业面临的重要挑战。Java 作为一门广泛使用的编程语言,在大数据处理领域同样发挥着关键作用。本文将深入探讨如何利用 Hadoop 和 Spark 这…...

使用Python绘制节日祝福——以端午节和儿童节为例
端午节 端午节总算是回家了,感觉时间过得真快,马上就毕业了,用Python弄了一个端午节元素的界面,虽然有点不像,祝大家端午安康。端午节粽子(python)_python画粽子-CSDN博客https://blog.csdn.net…...
探索大语言模型(LLM):参数量背后的“黄金公式”与Scaling Law的启示
引言 过去十年,人工智能领域最震撼的变革之一,是模型参数量从百万级飙升至万亿级。从GPT-3的1750亿参数到GPT-4的神秘规模,再到谷歌Gemini的“多模态巨兽”,参数量仿佛成了AI能力的代名词。但参数真的是越多越好吗?这…...
Excel to JSON 插件 2.4.0 版本更新
我们很高兴地宣布 Excel to JSON 插件已升级到 2.4.0 版本!本次更新带来了两项重要功能,旨在为您提供更大的灵活性和更强大的数据处理能力。 主要更新内容: 1. 用户可以选择从行或列中选择标题 在之前的版本中,插件通常默认从第…...
黑马点评后端笔记
1.基于Session实现登录流程 发送验证码: 先前端校验,后端再校验(防小人),合法生成验证码(RandomUtil生成),后端保存,在通过短信去发送给用户 短信验证码登录和注册: 拿到验证码和手机号后,后端通过session(spring mvc注入)拿到验证码,进行校验,如果用户…...

C#项目07-二维数组的随机创建
实现需求 创建二维数组,数组的列和宽为随机,数组内的数也是随机 知识点 1、Random类 Public Random rd new Random(); int Num_Int rd.Next(1, 100);2、数组上下限。 //定义数组 int[] G_Array new int[1,2,3,4];//一维数组 int[,] G_Array_T …...

光伏功率预测 | LSTM多变量单步光伏功率预测(Matlab完整源码和数据)
光伏功率预测 | MATLAB实现基于LSTM长短期记忆神经网络的光伏功率预测 目录 光伏功率预测 | MATLAB实现基于LSTM长短期记忆神经网络的光伏功率预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 光伏功率预测 | LSTM多变量单步光伏功率预测(Matlab完整源码和…...
解锁 AI 大语言模型的“知识宝藏”:知识库的奥秘与优化之道
1. 知识库在 AI 大语言模型中的作用 1.1 提供准确信息 知识库是 AI 大语言模型的重要组成部分,能够为模型提供准确的信息。在处理用户问题时,模型可以参考知识库中的数据,从而给出更准确的答案。例如,在医疗领域,知识…...

一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录——3. 服务器软件更新,以及常用软件安装
前言 前面,我们已经 安装好了 Ubuntu 服务器系统,并且 配置好了 ssh 免密登录服务器 ,现在,我们要来进一步的设置服务器。 那么,本文,就是进行服务器的系统更新,以及常用软件的安装 调整 Ubu…...
第四十天打卡
知识点回顾: 彩色和灰度图片测试和训练的规范写法:封装在函数中展平操作:除第一个维度batchsize外全部展平dropout操作:训练阶段随机丢弃神经元,测试阶段eval模式关闭dropout 作业:仔细学习下测试和训练代码…...
【请关注】ELK集群部署真实案例分享
ELK集群部署 1,准备es配置 es.yml: -------------------------------------------------------------- #集群名称 cluster.name: elasticsearch-cluster #节点名称 node.name: es-node1 #设置绑定的ip地址,可以使ipv4或者ipv6 #绑定这台机器的任何一个ip network.bind_hos…...
odoo17 windows server布署错误分析
odoo17 windows server布署错误分析 错误代码: File "C:\od172406\odoo\sql_db.py", line 681, in borrow result psycopg2.connect( ^^^^^^^^^^^^^^^^^ File "C:\od172406\venv\Lib\site-packages\psycopg2\__init__.py"…...

PyTorch 入门学习笔记
一、简介 PyTorch 是由 Meta(原 Facebook) 开源的深度学习框架。其前身 Torch 是一个基于 LuaJIT 的科学计算框架,核心功能是提供高效的张量(Tensor)操作和神经网络支持。由于 Lua 语言的生态限制,Torch 逐…...
【 Samba】Windows 用户访问Docker服务器上当前A用户的 ~/aaa目录
要让 Windows 用户访问 ~/aaa目录,需要在 Linux 系统上配置 Samba 共享服务,并设置合适的权限。以下是具体步骤: 1. 安装 Samba bash sudo apt update sudo apt install samba 2. 创建 Samba 用户(可选) 如果你希望 …...

pycharm生成图片
文章目录 图片例子生成图片并储存,设置中文字体支持两条线绘制散点图和直方图绘制条形图(bar)绘制条形图(横着的)(plt.barh)分组的条形图 颜色和线条风格1. **颜色字符 (color)**其他颜色指定方…...

Android 云手机横屏模式下真机键盘遮挡输入框问题处理
一、背景 打开横屏应用,点击云机EditText输入框,输入框被键盘遮挡,如下图: 未打开键盘状态: 点击第二个输入框,键盘遮挡了输入框: 二、解决方案(推荐第三中方案,博主采用的也是第三种方案) 博主这里整理了三种方案:…...
Redis 中的 5 种数据类型和示例场景
Redis 作为一款高性能的键值对数据库,凭借其丰富的数据类型,在缓存、消息队列、排行榜等众多场景中发挥着重要作用。本文将详细介绍 Redis 的 5 种核心数据类型,并结合示例场景和代码,让你快速掌握它们的使用方法。 一、String&am…...