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

文章目录

  • [0. 个人感悟](#0. 个人感悟)
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析](#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 代码示例

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

命令接口:

java 复制代码
public interface Command {  
    /**  
     * @description 执行  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     void execute();  
  
    /**  
     * @description 撤销  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     void undo();  
}

灯的接受者和具体命令:

java 复制代码
public class LightReceiver {  
    /**  
     * @description 开灯  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     public void on() {  
        System.out.println("开灯了");  
    }  
  
    /**  
     * @description 关灯  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     public void off() {  
        System.out.println("关灯了");  
    }  
}

public class LightOnCommand implements Command {  
    private LightReceiver lightReceiver;  
      
    public LightOnCommand(LightReceiver lightReceiver) {  
        this.lightReceiver = lightReceiver;  
    }  
  
    @Override  
    public void execute() {  
        lightReceiver.on();  
    }  
  
    @Override  
    public void undo() {  
        lightReceiver.off();  
    }  
}

public class LightOffCommand implements Command {  
    private LightReceiver lightReceiver;  
  
    public LightOffCommand(LightReceiver lightReceiver) {  
        this.lightReceiver = lightReceiver;  
    }  
  
    @Override  
    public void execute() {  
        lightReceiver.off();  
    }  
  
    @Override  
    public void undo() {  
        lightReceiver.on();  
    }  
}

电视接受者和命令:

java 复制代码
public class TVReceiver {  
    /**  
     * @description 开机  
     * @author bigHao  
     * @date 2026/1/20  
     **/    public void on() {  
        System.out.println("电视开了");  
    }  
  
    /**  
     * @description 关机  
     * @author bigHao  
     * @date 2026/1/20  
     **/    public void off() {  
        System.out.println("电视关了");  
    }  
}

public class TVOnCommand implements Command {  
    private TVReceiver receiver;  
  
    public TVOnCommand(TVReceiver receiver) {  
        this.receiver = receiver;  
    }  
  
  
    @Override  
    public void execute() {  
        receiver.on();  
    }  
  
    @Override  
    public void undo() {  
        receiver.off();  
    }  
}

public class TVOffCommand implements Command {  
    private TVReceiver receiver;  
  
    public TVOffCommand(TVReceiver receiver) {  
        this.receiver = receiver;  
    }  
  
  
    @Override  
    public void execute() {  
        receiver.off();  
    }  
  
    @Override  
    public void undo() {  
        receiver.on();  
    }  
}

遥控器:

java 复制代码
public class RemoteController {  
    public static final int INIT_COMMAND_NUM = 5;  
    private Command[] onCommands;  
    private Command[] offCommands;  
    private Command undoCommand;  
  
    public RemoteController() {  
        onCommands = new Command[INIT_COMMAND_NUM];  
        offCommands = new Command[INIT_COMMAND_NUM];  
        for (int i = 0; i < INIT_COMMAND_NUM; i++) {  
            onCommands[i] = new NoCommand();  
            offCommands[i] = new NoCommand();  
        }  
    }  
  
    public void setOnCommand(int no, Command onCommand, Command offCommand) {  
        onCommands[no] = onCommand;  
        offCommands[no] = offCommand;  
    }  
  
    public void on(int no) {  
        onCommands[no].execute();  
        // 记录当前操作  
        undoCommand = onCommands[no];  
    }  
  
    public void off(int no) {  
        offCommands[no].execute();  
        // 记录当前操作  
        undoCommand = offCommands[no];  
    }  
  
    public void undo() {  
        undoCommand.undo();  
    }  
}

测试:

java 复制代码
public class Client {  
  
    public static final int LIGHT_NO = 0;  
    public static final int TV_NO = 1;  
  
    static void main() {  
        RemoteController remoteController = new RemoteController();  
        LightReceiver lightReceiver = new LightReceiver();  
        LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);  
        LightOffCommand lightOffCommand = new LightOffCommand(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();  
  
        TVReceiver tvReceiver = new TVReceiver();  
        TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);  
        TVOffCommand tvOffCommand = new TVOffCommand(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接口

java 复制代码
// Runnable就是命令接口
public interface Runnable {
    public abstract void run();  // execute()方法
}

// Thread作为Invoker
Thread thread = new Thread(() -> 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方法。

参考:

相关推荐
zhangrelay2 小时前
ROS云课三分钟-cmake默认版本和升级-260120
笔记·学习
Maddie_Mo3 小时前
智能体设计模式 第二章:路由模式
设计模式
sycmancia3 小时前
C语言学习07——变量的作用域
c语言·学习
虾说羊3 小时前
Langchain4j中AIService学习
学习
代码游侠4 小时前
嵌入式开发——ARM Cortex-A7内核和i.MX6处理器相关的底层头文件
arm开发·笔记·嵌入式硬件·学习·架构
优雅的潮叭5 小时前
c++ 学习笔记之 volatile与atomic
c++·笔记·学习
啊阿狸不会拉杆5 小时前
《机器学习》第四章-无监督学习
人工智能·学习·算法·机器学习·计算机视觉
Duang007_6 小时前
【万字学习总结】API设计与接口开发实战指南
开发语言·javascript·人工智能·python·学习
啊阿狸不会拉杆6 小时前
《机器学习》第三章 - 监督学习
人工智能·深度学习·学习·机器学习·计算机视觉