行为设计模式之Command (命令)

行为设计模式之Command (命令)

前言:

需要发出请求的对象(调用者)和接收并执行请求的对象(执行者)之间没有直接依赖关系时。比如遥控器 每个按钮绑定一个command对象,这个Command对象内部持有真正执行操作的Receiver(如文档编辑器、打印服务)的引用,并知道调用Receiver的哪个方法。改变按钮功能只需更换绑定的Command对象。

1)意图

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

2)结构

其中:

  • Command 声明执行器的操作接口。
  • ConcreteCommand 将一个接收者绑定于一个动作:调用接收者相应的操作,以实现Execute。
  • Client 创建一个具体命令对象并设定它的接收者。
  • Invoker 要求该命令执行这个请求。
  • Receiver 知道如何实施于执行一个请求的操作。任何类都可能作为一个接收者。

3)适用性

Command 模式适用于:

  • 抽象出待执行的动作以参数化某对象。
  • 在不同的时刻指定、排列和执行请求。
  • 支持取消操作。
  • 支持修改日志。
  • 用构建在原语操作上的高层操作构造一个系统。
c 复制代码
/**
 * @author psd 行为设计模式之命令模式
 */
public class CommandPattern {
    public static void main(String[] args) {
        // 接收者
        Tv tv = new Tv();
        // 命令对象开
        Command openCommand = new OpenTvCommand(tv);
        // 命令对象关闭
        Command closeCommand = new CloseTvCommand(tv);
        Invoker invoker = new Invoker();
        invoker.setCommand(openCommand);
        invoker.executeCommand();
        System.out.println("-----------------");
        invoker.setCommand(closeCommand);
        invoker.executeCommand();
    }
}

/**
 * 请求者
 */
class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void executeCommand() {
        command.execute();
    }
}

interface Command {
    /**
     * 执行命令
     */
    void execute();
}

class CloseTvCommand implements Command {

    private Tv tv;

    public CloseTvCommand(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.close();
    }
}

/**
 * 打开电视机
 */
class OpenTvCommand implements Command {

    private Tv tv;

    public OpenTvCommand(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.open();
    }
}

class Tv {
    public void open() {
        System.out.println("打开电视机");
    }

    public void close() {
        System.out.println("关闭电视机");
    }
}

以下是命令模式最常见的应用场景:

1、请求调用者与执行者解耦:

场景描述: 当需要让发出请求的对象(调用者)和接收并执行请求的对象(执行者)之间没有直接的依赖关系时。

典型例子:

GUI 按钮/菜单操作: 一个按钮(调用者)不知道点击后具体要执行什么操作(打开文件、保存、打印)。按钮只绑定一个Command对象。这个Command对象内部持有真正执行操作的Receiver(如文档编辑器、打印服务)的引用,并知道调用Receiver的哪个方法。改变按钮功能只需更换绑定的Command对象。

遥控器/智能家居: 遥控器(调用者)上的按钮不知道具体控制哪个设备(灯、风扇、电视 - 执行者)。每个按钮绑定一个Command对象(如LightOnCommand),该对象知道如何调用特定设备(Light)的方法(turnOn())。

好处: 调用者完全不需要知道执行者的具体类型和接口,只需知道如何调用命令对象的execute()方法。极大地提高了系统的灵活性和可扩展性。

2、实现操作的队列化、调度与延迟执行:

场景描述: 需要将请求放入队列中排队执行,或者安排请求在特定时间执行(延迟执行),或者在后台线程执行。

典型例子:

线程池/任务队列: 将Command对象(代表任务)提交到线程池的工作队列中。线程池的工作线程从队列中取出Command对象并调用其execute()方法。线程池本身只关心命令接口,不关心具体任务是什么。

批处理操作: 用户执行一系列操作(如多个文件操作),可以先将每个操作封装成Command对象,放入一个队列。然后一次性按顺序(或根据策略)执行整个队列的命令。

日志记录与重放/事务系统: 记录执行的命令序列,可以在系统崩溃后重新执行这些命令来恢复状态,或者用于审计、回滚事务。

好处: 命令对象封装了执行所需的所有信息(接收者、方法、参数),使其易于存储、传输和调度。

3、实现撤销(Undo)和重做(Redo)功能:

场景描述: 这是命令模式最经典的应用之一。系统需要支持撤销用户最近的操作,或者重新执行被撤销的操作。

典型例子:

文本编辑器: 每次编辑操作(插入、删除、格式化)都封装成一个Command对象(如InsertCommand, DeleteCommand)。命令对象在执行execute()时会记录执行前的状态(如被删除的文本及其位置)。命令对象还需要实现unexecute()或undo()方法,利用记录的信息恢复到执行前的状态。一个历史栈保存已执行的命令,撤销就是弹出栈顶命令并调用其undo();重做则是将撤销栈顶的命令重新执行并压入执行栈。

绘图软件: 类似文本编辑器,每个绘图操作(画线、添加形状、移动、改变颜色)都封装成命令对象,支持撤销/重做。

好处: 命令对象天然地封装了执行操作和撤销操作所需的所有信息(接收者、执行方法、执行前的状态)。管理命令历史栈是实现撤销/重做的关键。

4、实现宏命令:

场景描述: 需要将一系列操作组合成一个单一的操作(宏)。

典型例子:

批处理脚本/自动化: 创建一个MacroCommand类,它本身也是一个Command对象,但内部持有一个Command对象的集合。调用MacroCommand.execute()会依次执行集合中所有命令的execute()方法。MacroCommand.undo()会按相反顺序调用所有命令的undo()方法。

复杂界面初始化/配置: 将多个界面设置操作组合成一个宏命令,一键应用所有配置。

好处: 利用组合模式,将简单命令组合成复杂命令,对调用者透明。

支持事务性行为:

场景描述: 需要确保一组操作要么全部成功执行,要么全部不执行(原子性)。

典型例子: 在执行一系列命令(代表数据库操作步骤)时,如果其中任何一步失败,可以调用之前所有已成功执行命令的undo()方法进行回滚,恢复到初始状态。

好处: 每个命令的execute()和undo()方法提供了实现简单事务语义的基础。

总结关键场景特征:

当你遇到以下需求时,考虑命令模式:

"把动作的请求者从动作的执行者对象中解耦出来。"

"需要在不同的时间点指定、排队和执行请求。"

"需要支持撤销操作。"

"需要支持记录日志,以便在系统崩溃时能重新执行这些命令恢复状态。"

"需要支持将一组操作组合成一个复合操作(宏命令)。"

喜欢我的文章记得点个在看,或者点赞,持续更新中ing...

相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔6 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai11 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说12 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json