23种设计模式一命令模式

设计模式是软件开发中的经典智慧,命令模式作为行为型模式的代表,为我们提供了一种优雅的方式来封装请求、解耦调用者与接收者。本文将从概念到实践,全面解析Java中的命令模式。


一、引言:为什么要学习命令模式?

1.1 什么是命令模式?

**命令模式(Command Pattern)**是一种行为型设计模式,它将一个请求封装为一个对象,从而允许用户使用不同的请求对客户进行参数化、对请求排队或记录请求日志,以及支持可撤销的操作。

核心思想:将"请求的发起者"和"请求的执行者"解耦,通过引入中间的命令对象来实现灵活的调用。

1.2 学习价值

  • 解耦优势:让调用者无需知道接收者的具体实现
  • 扩展性强:新增命令无需修改现有代码
  • 支持高级功能:轻松实现撤销、重做、队列、日志等
  • 提升代码可维护性:职责清晰,符合单一职责原则

二、命令模式的结构与角色

2.1 四大核心角色

复制代码
┌─────────────────────────────────────────────────────────┐
│                    调用者 (Invoker)                      │
│   - 持有命令对象                                         │
│   - 负责调用命令的执行方法                               │
└───────────────────┬─────────────────────────────────────┘
                    │
                    │ 持有
                    ▼
┌─────────────────────────────────────────────────────────┐
│          命令接口 (Command)                              │
│   - 声明执行方法:execute()                              │
└───────────────────┬─────────────────────────────────────┘
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
┌───────────────┐       ┌───────────────┐
│ 具体命令A      │       │ 具体命令B      │
│ ConcreteCmdA   │       │ ConcreteCmdB   │
└───────┬───────┘       └───────┬───────┘
        │                       │
        │ 持有                   │ 持有
        ▼                       ▼
┌─────────────────────────────────────────────────────────┐
│                接收者 (Receiver)                        │
│   - 真正执行业务逻辑的对象                               │
└─────────────────────────────────────────────────────────┘

2.2 角色详解

角色 职责 示例
Command(命令接口) 声明执行操作的抽象接口 interface Command { void execute(); }
ConcreteCommand(具体命令) 实现命令接口,绑定接收者 LightOnCommand, LightOffCommand
Invoker(调用者) 持有命令对象并调用执行 RemoteControl
Receiver(接收者) 执行实际业务逻辑 Light, TV

2.3 UML类图

复制代码
┌─────────────────┐
│   <<interface>> │
│    Command      │
├─────────────────┤
│ +execute(): void│
└────────┬────────┘
         │
         │ implements
         ▼
┌──────────────────────────────┐
│     ConcreteCommand          │
├──────────────────────────────┤
│ -receiver: Receiver          │
├──────────────────────────────┤
│ +ConcreteCommand(receiver)   │
│ +execute(): void             │
└─────────┬────────────────────┘
          │
          │ holds
          ▼
┌──────────────────────────────┐
│       Receiver               │
├──────────────────────────────┤
│ +action(): void              │
└──────────────────────────────┘

┌──────────────────────────────┐
│       Invoker                │
├──────────────────────────────┤
│ -command: Command            │
├──────────────────────────────┤
│ +setCommand(cmd: Command)    │
│ +invoke(): void              │
└──────────────────────────────┘

三、实现步骤:智能家居遥控器案例

3.1 场景说明

我们以"智能家居遥控器"为例:遥控器可以控制电灯、电视等家电的开/关操作。

3.2 第一步:定义命令接口

java 复制代码
/**
 * 命令接口:所有具体命令的抽象
 */
public interface Command {
    /**
     * 执行命令
     */
    void execute();
    
    /**
     * 撤销命令(可选功能)
     */
    void undo();
}

3.3 第二步:创建接收者

java 复制代码
/**
 * 接收者:电灯
 */
public class Light {
    private String location;
    
    public Light(String location) {
        this.location = location;
    }
    
    /**
     * 开灯操作
     */
    public void on() {
        System.out.println(location + " 的灯已打开");
    }
    
    /**
     * 关灯操作
     */
    public void off() {
        System.out.println(location + " 的灯已关闭");
    }
}

/**
 * 接收者:电视
 */
public class TV {
    private String location;
    
    public TV(String location) {
        this.location = location;
    }
    
    public void on() {
        System.out.println(location + " 的电视已打开");
    }
    
    public void off() {
        System.out.println(location + " 的电视已关闭");
    }
    
    public void setInputChannel(int channel) {
        System.out.println(location + " 的电视已切换到频道 " + channel);
    }
}

3.4 第三步:实现具体命令

java 复制代码
/**
 * 具体命令:开灯命令
 */
public class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        light.on();
    }
    
    @Override
    public void undo() {
        light.off();
    }
}

/**
 * 具体命令:关灯命令
 */
public class LightOffCommand implements Command {
    private Light light;
    
    public LightOffCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        light.off();
    }
    
    @Override
    public void undo() {
        light.on();
    }
}

/**
 * 具体命令:打开电视命令
 */
public class TVOnCommand implements Command {
    private TV tv;
    private int previousChannel;
    
    public TVOnCommand(TV tv) {
        this.tv = tv;
        this.previousChannel = 1; // 默认频道
    }
    
    @Override
    public void execute() {
        tv.on();
        tv.setInputChannel(previousChannel);
    }
    
    @Override
    public void undo() {
        tv.off();
    }
}

3.5 第四步:创建调用者

java 复制代码
/**
 * 调用者:遥控器
 * 支持多个按钮,每个按钮可以绑定不同的命令
 */
public class RemoteControl {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command undoCommand; // 用于撤销操作
    
    /**
     * 初始化遥控器,支持7个插槽
     */
    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];
        
        // 初始化为空命令(避免空指针)
        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }
    
    /**
     * 设置插槽的命令
     */
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
    
    /**
     * 按下开按钮
     */
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }
    
    /**
     * 按下关按钮
     */
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }
    
    /**
     * 撤销上一次操作
     */
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
    
    /**
     * 显示遥控器状态
     */
    public void toStringDisplay() {
        System.out.println("\n------ 遥控器状态 ------");
        for (int i = 0; i < onCommands.length; i++) {
            System.out.println("[插槽 " + i + "] " + 
                onCommands[i].getClass().getName() + "   " + 
                offCommands[i].getClass().getName());
        }
        System.out.println("[撤销] " + undoCommand.getClass().getName());
    }
}

/**
 * 空命令:默认命令,什么也不做
 * 用于初始化数组,避免空指针异常
 */
public class NoCommand implements Command {
    @Override
    public void execute() {
        // 什么也不做
    }
    
    @Override
    public void undo() {
        // 什么也不做
    }
}

3.6 第五步:客户端使用

java 复制代码
/**
 * 客户端:测试命令模式
 */
public class RemoteControlTest {
    public static void main(String[] args) {
        // 1. 创建接收者
        Light livingRoomLight = new Light("客厅");
        Light kitchenLight = new Light("厨房");
        TV livingRoomTV = new TV("客厅");
        
        // 2. 创建具体命令
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
        TVOnCommand livingRoomTVOn = new TVOnCommand(livingRoomTV);
        
        // 3. 创建调用者(遥控器)并设置命令
        RemoteControl remote = new RemoteControl();
        remote.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remote.setCommand(1, kitchenLightOn, kitchenLightOff);
        remote.setCommand(2, livingRoomTVOn, livingRoomTVOn);
        
        // 4. 显示遥控器状态
        remote.toStringDisplay();
        
        // 5. 测试功能
        System.out.println("\n------ 测试客厅灯 ------");
        remote.onButtonWasPushed(0);
        remote.offButtonWasPushed(0);
        
        System.out.println("\n------ 测试撤销 ------");
        remote.onButtonWasPushed(0);  // 打开灯
        remote.undoButtonWasPushed(); // 撤销:关闭灯
        
        System.out.println("\n------ 测试厨房灯 ------");
        remote.onButtonWasPushed(1);
        
        System.out.println("\n------ 测试电视 ------");
        remote.onButtonWasPushed(2);
    }
}

输出结果:

复制代码
------ 遥控器状态 ------
[插槽 0] LightOnCommand   LightOffCommand
[插槽 1] LightOnCommand   LightOffCommand
[插槽 2] TVOnCommand   TVOnCommand
[插槽 3] NoCommand   NoCommand
[插槽 4] NoCommand   NoCommand
[插槽 5] NoCommand   NoCommand
[插槽 6] NoCommand   NoCommand
[撤销] NoCommand

------ 测试客厅灯 ------
客厅 的灯已打开
客厅 的灯已关闭

------ 测试撤销 ------
客厅 的灯已打开
客厅 的灯已关闭

------ 测试厨房灯 ------
厨房 的灯已打开

------ 测试电视 ------
客厅 的电视已打开
客厅 的电视已切换到频道 1

四、优缺点分析

4.1 主要优势

优势 说明
解耦 调用者与接收者完全解耦,互不依赖
扩展性强 新增命令只需实现Command接口,符合开闭原则
支持撤销/重做 通过保存命令状态,轻松实现撤销操作
组合操作 可将多个命令组合为宏命令(Composite模式)
延迟执行 命令可以在需要的时刻才执行
日志与排队 方便记录操作日志或实现任务队列

4.2 潜在缺点

缺点 说明 缓解方案
类数量增加 每个具体命令都是一个类 使用Lambda表达式(Java 8+)或匿名内部类
系统复杂度 引入额外抽象层,理解成本增加 明确文档注释,合理使用场景
内存开销 大量命令对象可能占用内存 使用命令池或享元模式优化

五、应用场景与实战案例

5.1 典型应用场景

  1. GUI事件处理:按钮点击、菜单选择等
  2. 事务管理:数据库操作的提交与回滚
  3. 任务队列与调度:后台任务异步执行
  4. 日志记录:记录操作历史用于审计
  5. 宏命令:批量执行一系列操作
  6. 多级撤销/重做:编辑器等应用的核心功能

5.2 Java标准库中的应用

1. java.lang.Runnable

java 复制代码
// Runnable是最简单的命令模式应用
public interface Runnable {
    public abstract void run();
}

// 使用示例
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("执行任务");
    }
});
thread.start();

2. java.swing.Action

Swing中的Action接口封装了按钮的行为和状态:

java 复制代码
Action saveAction = new AbstractAction("保存", saveIcon) {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 执行保存操作
        saveDocument();
    }
};

5.3 框架中的应用

Spring框架:事务管理

java 复制代码
@Transactional
public void transferMoney(Account from, Account to, double amount) {
    // Spring通过命令模式封装事务操作
    from.debit(amount);
    to.credit(amount);
}

Struts/Hibernate:Action与拦截器

这些框架使用命令模式将HTTP请求封装为Action对象,由框架统一调度。


六、与其他模式的对比

6.1 命令模式 vs 策略模式

对比维度 命令模式 策略模式
目的 封装请求,支持撤销/重做 封装算法, interchangeable
关注点 操作的执行过程 算法的不同实现
状态 可保存执行状态 通常无状态
使用场景 GUI操作、事务、宏命令 算法替换、业务规则变化

代码对比:

java 复制代码
// 策略模式:关注算法本身
public interface SortStrategy {
    void sort(int[] array);
}

// 命令模式:关注操作的封装与执行
public interface Command {
    void execute();
    void undo();
}

6.2 命令模式 vs 状态模式

对比维度 命令模式 状态模式
变化维度 操作的变化 状态的变化
行为触发 调用者主动调用 状态内部自动切换
适用场景 解耦请求与处理 对象行为随状态变化

6.3 命令模式 vs 责任链模式

  • 命令模式:命令被明确接收者执行
  • 责任链模式:请求沿链传递,直到被处理

七、总结与最佳实践

7.1 核心价值提炼

命令模式的本质是**"将请求封装为对象"**,通过这一简单思想实现了:

  • 调用者与接收者的彻底解耦
  • 操作的可记录、可撤销、可延迟执行
  • 系统的高扩展性和灵活性

7.2 使用建议

推荐使用的场景:

  • 需要支持撤销/重做操作
  • 需要将请求排队或异步执行
  • GUI事件处理框架
  • 事务管理系统
  • 需要组合多个操作

不建议使用的场景:

  • 简单的一次性操作
  • 命令数量极少且不会扩展
  • 对性能要求极高的场景

7.3 优化技巧

java 复制代码
// Java 8+:使用Lambda简化命令
remote.setCommand(0, 
    () -> light.on(), 
    () -> light.off()
);

// 宏命令:组合多个命令
public class MacroCommand implements Command {
    private Command[] commands;
    
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }
    
    @Override
    public void execute() {
        for (Command cmd : commands) {
            cmd.execute();
        }
    }
}

最后的话:设计模式不是僵化的教条,而是解决问题的智慧。在实际项目中,根据业务需求灵活运用命令模式,才能真正发挥它的价值。

相关推荐
J_liaty12 小时前
23种设计模式一迭代器模式
设计模式·迭代器模式
驴儿响叮当201016 小时前
设计模式之策略模式
设计模式·策略模式
驴儿响叮当201018 小时前
设计模式之中介模式
设计模式
驴儿响叮当201021 小时前
设计模式之命令模式
设计模式·命令模式
驴儿响叮当20101 天前
设计模式之适配器模式
设计模式·适配器模式
HEU_firejef1 天前
设计模式——代理模式
设计模式·代理模式
Coder_Boy_1 天前
从单体并发工具类到分布式并发:思想演进与最佳实践(二)
java·spring boot·分布式·微服务·设计模式
TvxzFtDBIxok1 天前
基于人工势场法的船舶自动避碰系统MATLAB实现之旅
命令模式
geovindu1 天前
python: Memento Pattern
开发语言·python·设计模式·备忘录模式