设计模式精讲 Day 14:命令模式(Command Pattern)

【设计模式精讲 Day 14】命令模式(Command Pattern)


文章内容

在“设计模式精讲”系列的第14天,我们来学习命令模式(Command Pattern)。命令模式是一种行为型设计模式,它将请求封装为对象,从而使你可以用不同的请求、队列或日志来参数化客户端。这种模式的核心思想是通过解耦请求的发起者和执行者,提升系统的灵活性与可扩展性。

命令模式在实际开发中广泛应用,例如在GUI事件处理、任务调度、事务管理等领域。它能够帮助开发者构建更清晰、更易于维护的系统架构。本篇文章将从理论到实践,深入讲解命令模式的定义、结构、实现方式以及实际应用案例,并结合Java代码展示其具体使用方式。


模式定义

命令模式(Command Pattern)是一种行为型设计模式,它将一个请求封装成一个对象,从而使你可以在不同的请求之间进行参数化传递,或者将请求排队、记录日志、撤销操作等。

核心思想
将请求的发送者与接收者解耦,使得请求可以被参数化、记录、撤销或重放。


模式结构

命令模式的UML类图包含以下几个关键角色:

  • Command(命令接口):声明执行操作的抽象方法。
  • ConcreteCommand(具体命令):实现Command接口,通常会持有接收者(Receiver)的引用,并调用其业务方法。
  • Receiver(接收者):知道如何执行与请求相关的操作。
  • Invoker(调用者):向客户端提供调用命令的方法,通常不直接与接收者交互。
  • Client(客户端):创建具体的命令对象,并设置其接收者。
类图文字描述
+---------------------+
|     Command         |
+---------------------+
| + execute()         |
+---------------------+^|
+---------------------+
| ConcreteCommand     |
+---------------------+
| - receiver: Receiver|
| + execute()         |
+---------------------+^|
+---------------------+
|     Receiver        |
+---------------------+
| + action()          |
+---------------------+^|
+---------------------+
|     Invoker         |
+---------------------+
| + setCommand()      |
| + executeCommand()  |
+---------------------+

适用场景

命令模式适用于以下几种典型场景:

场景描述
请求的参数化需要将请求作为参数传递给其他对象
请求的队列将多个请求放入队列中异步执行
日志记录记录所有操作以便后续恢复或调试
撤销/重做功能通过保存命令对象实现操作的回滚
事件驱动系统在GUI或消息队列中处理用户输入或消息

实现方式

下面是一个完整的Java代码示例,演示了命令模式的基本实现。

1. 定义命令接口
/*** 命令接口:定义执行操作的抽象方法*/
public interface Command {void execute();
}
2. 定义接收者类
/*** 接收者:执行具体业务逻辑的对象*/
public class Receiver {public void action() {System.out.println("Receiver is performing the action.");}
}
3. 定义具体命令类
/*** 具体命令类:实现Command接口,持有接收者的引用*/
public class ConcreteCommand implements Command {private Receiver receiver;public ConcreteCommand(Receiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {// 调用接收者的业务方法receiver.action();}
}
4. 定义调用者类
/*** 调用者:负责调用命令对象的execute方法*/
public class Invoker {private Command command;public void setCommand(Command command) {this.command = command;}public void executeCommand() {if (command != null) {command.execute();} else {System.out.println("No command to execute.");}}
}
5. 客户端代码
/*** 客户端:创建命令对象并设置接收者*/
public class Client {public static void main(String[] args) {Receiver receiver = new Receiver();Command command = new ConcreteCommand(receiver);Invoker invoker = new Invoker();invoker.setCommand(command);invoker.executeCommand(); // 输出:Receiver is performing the action.}
}

工作原理

命令模式通过将请求封装为对象,实现了请求的解耦。调用者(Invoker)不再直接与接收者(Receiver)交互,而是通过命令对象间接调用。这使得系统更加灵活,可以轻松地添加新的命令类型而无需修改现有代码。

此外,由于命令对象是独立的,它们可以被存储、传递、重复使用甚至撤销。例如,在实现撤销功能时,只需要保存命令对象的历史记录即可。


优缺点分析

优点缺点
解耦请求的发送者与接收者增加了系统的复杂度
支持请求的排队、日志和撤销可能导致过多的命令类
易于扩展新的命令类型增加了内存消耗(每个命令都是一个对象)

案例分析:任务调度系统

假设我们正在开发一个任务调度系统,需要支持异步执行任务、记录日志和撤销操作。使用命令模式可以很好地解决这些问题。

问题描述:
  • 用户提交任务后,需要异步执行
  • 需要记录每条任务的执行日志
  • 需要支持撤销最近一次操作
解决方案:

使用命令模式封装每个任务,由调度器统一管理。每次执行任务时,同时记录日志;撤销操作时,只需调用对应的命令对象的undo()方法。

示例代码:
// 扩展命令接口,增加 undo 方法
public interface Command {void execute();void undo();
}// 修改接收者
public class TaskReceiver {public void executeTask() {System.out.println("Executing task...");}public void undoTask() {System.out.println("Undoing task...");}
}// 修改具体命令类
public class TaskCommand implements Command {private TaskReceiver receiver;private boolean executed = false;public TaskCommand(TaskReceiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {if (!executed) {receiver.executeTask();executed = true;}}@Overridepublic void undo() {if (executed) {receiver.undoTask();executed = false;}}
}// 修改调用者
public class TaskScheduler {private Command currentCommand;public void setCommand(Command command) {this.currentCommand = command;}public void executeCommand() {if (currentCommand != null) {currentCommand.execute();}}public void undoCommand() {if (currentCommand != null) {currentCommand.undo();}}
}// 客户端测试
public class TaskClient {public static void main(String[] args) {TaskReceiver receiver = new TaskReceiver();Command command = new TaskCommand(receiver);TaskScheduler scheduler = new TaskScheduler();scheduler.setCommand(command);scheduler.executeCommand(); // 执行任务scheduler.undoCommand();    // 撤销任务}
}

在这个案例中,命令模式不仅提升了系统的可扩展性,还使任务的执行、记录和撤销变得非常简单。


与其他模式的关系

命令模式常与其他设计模式结合使用,以增强系统功能:

模式说明
责任链模式命令模式可以作为责任链中的一个节点,实现请求的链式处理
备忘录模式命令模式可以与备忘录模式配合,用于实现撤销功能
迭代器模式可以将命令对象封装为迭代器,实现对命令集合的遍历
组合模式命令模式可以嵌套使用,形成复杂的操作序列

总结

今天我们详细学习了命令模式(Command Pattern)。通过将请求封装为对象,命令模式实现了请求的解耦,提高了系统的灵活性和可维护性。我们在Java中通过完整的代码示例展示了该模式的实现方式,并结合任务调度系统的实际案例进行了分析。

命令模式体现了面向对象设计中的单一职责原则(SRP)和开闭原则(OCP),因为每个命令类只关注自己的业务逻辑,且可以通过扩展命令类来满足新需求。

在接下来的Day 15中,我们将进入**解释器模式(Interpreter Pattern)**的学习,探索如何为语言或表达式定义文法并解析其含义。敬请期待!


文章标签

design-patterns, java, command-pattern, software-design, object-oriented-programming


文章简述

本文深入讲解了“设计模式精讲”系列的第14天——命令模式(Command Pattern)。文章从理论出发,介绍了命令模式的核心思想、结构组成和适用场景,并通过完整的Java代码示例展示了其基本实现方式。我们还通过一个任务调度系统的实际案例,说明了命令模式在真实项目中的应用价值。此外,文章还对比了命令模式与其他相关设计模式的关系,并分析了其优缺点。通过对命令模式的全面讲解,读者可以更好地理解如何在实际开发中利用这一模式提高系统的灵活性和可维护性。


进一步学习资料

  1. Design Patterns: Elements of Reusable Object-Oriented Software
  2. Refactoring Guru - Command Pattern
  3. Java Design Patterns - Command Pattern
  4. Martin Fowler’s Patterns of Enterprise Application Architecture - Command
  5. Java Design Patterns Book - Command Pattern Example

核心设计思想总结

命令模式的核心在于将请求封装为对象,从而实现请求的参数化、记录、撤销等功能。在实际项目中,我们可以使用命令模式来构建任务调度系统、GUI事件处理、事务管理等模块,显著提升系统的可扩展性和可维护性。通过将请求的发起者与执行者解耦,命令模式让系统更加灵活,也更容易应对未来的变化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87097.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87097.shtml

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

相关文章

手机射频功放测试学习(二)——手机线性功放的静态电流和小信号(S-Parameter)测试

目录 一、概要 二、LPA的电流测试 1、LPA的泄漏电流测试 手动测试步骤如下: 自动化测试: 2、LPA的静态电流测试 手动测试步骤如下: 自动化测试: 三、LPA的S-Parameter测试 1、矢量网络分析仪校准 2、LPA的S参数手动测试步骤: 3、LPA的S参数自动测试步骤: 四…

基础算法合集-图论

本文将介绍数据结构图论部分中常见的算法 单源最短路径问题(用来计算一个点到其他所有顶点的最短路径) Dijkstra(n*n) 1. 初始化: 先找出从源点V0到各终点Vk的直达路径(V0,Vk), 即通过一条弧到达的路径 2. 选择: 从这些路径中找出一条长度最短的路径(V0,u) 3. 更新: 然后对其余…

vue-i18n 插件打包解析失效问题记录

vue-i18n 插件打包解析失效问题记录 开发环境中没有问题的,但打包发布之后就不行了,显示的就是模板字符串 // An highlighted block const messages {en: {step: {stepDesc1: Scan,stepDesc2: Analyze,stepDesc3: Result}},zh: {step: {stepDesc1: 扫描…

数据可视化 - 单子图

一、认识单子图 import matplotlib.pyplot as plt import numpy as np import pandas as pdplt.figure(num单子图, figsize(12, 8), facecolorw) # 中文字体 plt.rcParams[font.sans-serif] KaiTi # 负号显示 plt.rcParams[axes.unicode_minus] False# 2行,1列&a…

服务器上设置了代理之后,服务器可以访问外网,但是不能访问服务器本地。如何解决

你在服务器上设置了代理后,发现: 可以访问外网不能访问服务器本地地址(如 localhost、127.0.0.1、内网IP) 这是代理设置中常见的问题,尤其是当你设置了全局 HTTP/HTTPS 代理时。本地访问也会被强制走代理&#xff0c…

mysql启动报错:Can‘t connect to local MySQL server through socket

文章目录 一、报错内容二、解决方法 一、报错内容 在linux上启动mysql时报错 [rootlocalhost bin]# ./mysql -u root -p Enter password: ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)执行以上命令后报错,并且也…

C# Avalonia 绑定模式 Mode 的区别,它们的应用场景

C# Avalonia 绑定模式 Mode 的区别,它们的应用场景 文章目录 1. **Default(默认模式)**2. **OneTime(一次性绑定)**3. **OneWay(单向绑定)**4. **TwoWay(双向绑定)**5. *…

【OpenGL学习】(七)纹理单元

【OpenGL学习】(七)纹理单元 OpenGL的纹理单元(Texture Unit)是GPU中用于管理和组织纹理资源的逻辑单元,它允许开发者在渲染过程中同时使用多个纹理,并通过采样器(Sampler)在着色器…

Ubuntu 下降 Linux Kernel 的版本备忘

此处以 ubuntu 22.04 为示例系统,来降低其 Linux kernel 的版本。 1. 降低 Linux kernel 版本 在 Ubuntu 22.04 上降低 Linux 内核版本的步骤如下所示。 步骤 1:检查当前内核版本 uname -r 确认当前运行的内核版本。 步骤 2:查看已安装的…

Python 数据分析与机器学习入门 (八):用 Scikit-Learn 跑通第一个机器学习模型

引言:初识 Scikit-Learn Scikit-learn 是 Python 机器学习领域的黄金标准库。它构建在 NumPy, SciPy 和 Matplotlib 之上,提供了大量用于分类、回归、聚类和降维等任务的算法。Scikit-learn 广受欢迎的原因在于其三大核心优势: 一致的 API 设…

FPGA芯片的配置方法

FPGA芯片的配置方法 文章目录 FPGA芯片的配置方法1. FPGA配置概述2. 主动配置模式3. 被动配置模式4. JTAG配置模式5. 总结 1. FPGA配置概述 当我们在PC机上的FPGA软件集成开发环境中完成我们的设计后,必须通过某种形式将其映射到FPGA芯片硬件中,这样FPG…

通过python+openCV实现对图片中箭头方向的判断

在项目中遇到一个需求,需要对图片中的箭头方向进行判断,本来是使用YOLOv8算法来实现的,但是发现YOLO的效果对箭头的识别效果很差,不管是分类算法还是检测算法,效果都不理想,因此试一试通过openCV对箭头方向进行判断,发现效果还可以。 下面附上完整的代码和原理。 文章目…

React 第六十六节Router中 StaticRouter使用详解及注意事项

前言 StaticRouter 是 React Router 为服务器端渲染(SSR)提供的专用路由组件。它允许在服务器环境中处理路由逻辑,确保服务器和客户端渲染结果一致。下面我将详细解释其用途、原理并提供完整的代码示例。 一、StaticRouter 的核心用途 服务…

嵌入模型与大语言模型的区别:从结构到应用的深度解析

嵌入模型与大语言模型的区别:从结构到应用的深度解析 在当今自然语言处理(NLP)技术蓬勃发展的背景下,嵌入模型(Embedding Model) 和 大语言模型(Large Language Model, LLM) 成为了…

el-date-picker赋值不成功

vue使用element 的时间组件el-date-picker赋值不成功,点击后才回显数据 解决: 组件未渲染完成之前赋值了,在onMounted函数内赋值,或者在确保组件已经渲染后赋值

深入浅出JavaScript中的私有变量与特权方法

深入浅出JavaScript中的私有变量与特权方法:封装的艺术 在JavaScript的开发实践中,私有变量和特权方法是实现数据封装和代码安全性的核心工具。它们不仅帮助我们隐藏敏感数据,还能通过闭包和作用域机制构建更健壮的代码结构。本文将从基础概…

ReactNative【实战系列教程】我的小红书 2 -- 快捷登录、手机号密码登录

最终效果 技术要点 用户协议 – 打开本地浏览器 点击后,直接打开本地浏览器浏览网页 // 最终需修改为 《用户协议》 的网址Linking.openURL("https://www.baidu.com");手机号输入框的 344 展示 onChangeText{(text: string) > {setPhone(formatPhone(…

【赵渝强老师】OceanBase数据库从零开始:Oracle模式

这里我们来介绍一下新上线的课程《OceanBase数据库从零开始:Oracle模式》,本门课程共11章。 视频讲解如下 【赵渝强老师】OceanBase从零开始(Oracle模式) 下面详细介绍一下每一章的主要内容: 第01章-OceanBase的体系…

Flink核心功能与运行流程详解

目录 一、背景 二、图构建 三、任务执行流程(yarn per-job模式) 3.1 Flink组件 3.2 执行流程 四、分布式调度 4.1 TM的slot 4.2 TM的slot的CPU与内存 4.3 节点的部署 4.4 节点的状态 4.5 节点部署流程 五、数据传输 5.1 内存分配 5.2 传输…

linux 操作docker的基本命令docker仓库

基本操作命令 docker run --nametest-host -itd centos7.6 /bin/bash 通过镜像创建容器 登录容器 [rootdocker101 ~]# docker exec -it test-host /bin/bash (exec是执行,i是交互式。t叫tty) 或者container id [rootdocker101 ~]# doc…