命令模式 (Command Pattern)

命令模式 (Command Pattern)

概述

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

意图

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

适用场景

  • 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
  • 需要在不同的时间指定请求、将请求排队和执行请求
  • 需要支持命令的撤销(Undo)和恢复(Redo)操作
  • 需要将一组操作组合在一起,即支持宏命令

结构

复制代码
┌─────────────┐          ┌─────────────┐
│   Client    │──────────>│  Invoker    │
├─────────────┤          ├─────────────┤
│             │          │ - command   │
└─────────────┘          │ + setCommand() │
                         │ + executeCommand() │
                         └─────────────┘
                                 ▲
                                 │
┌─────────────┐          ┌─────────────┐
│  Command    │<─────────│  Receiver   │
├─────────────┤          ├─────────────┤
│ + execute() │          │ + action()  │
│ + undo()    │          └─────────────┘
└─────────────┘
        ▲
        │
┌─────────────┐
│ConcreteCommand│
├─────────────┤
│ - receiver  │
│ + execute() │
│ + undo()    │
└─────────────┘

参与者

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

示例代码

下面是一个完整的命令模式示例,以遥控器控制电器为例:

java 复制代码
// Receiver - 接收者
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 Stereo {
    private String location;
    
    public Stereo(String location) {
        this.location = location;
    }
    
    public void on() {
        System.out.println(location + " 音响已打开");
    }
    
    public void off() {
        System.out.println(location + " 音响已关闭");
    }
    
    public void setCD() {
        System.out.println(location + " 音响已设置CD");
    }
    
    public void setVolume(int volume) {
        System.out.println(location + " 音响音量设置为 " + volume);
    }
}

// Command - 命令接口
public interface Command {
    void execute();
    void undo();
}

// ConcreteCommand - 具体命令1
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();
    }
}

// ConcreteCommand - 具体命令2
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();
    }
}

// ConcreteCommand - 具体命令3
public class StereoOnWithCDCommand implements Command {
    private Stereo stereo;
    
    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }
    
    @Override
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
    
    @Override
    public void undo() {
        stereo.off();
    }
}

// ConcreteCommand - 具体命令4
public class StereoOffCommand implements Command {
    private Stereo stereo;
    
    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }
    
    @Override
    public void execute() {
        stereo.off();
    }
    
    @Override
    public void undo() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
}

// ConcreteCommand - 空命令
public class NoCommand implements Command {
    @Override
    public void execute() {
        System.out.println("什么也不做");
    }
    
    @Override
    public void undo() {
        System.out.println("什么也不做");
    }
}

// Invoker - 调用者
public class RemoteControl {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command undoCommand;
    
    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();
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建接收者
        Light livingRoomLight = new Light("客厅");
        Light kitchenLight = new Light("厨房");
        Stereo stereo = new Stereo("客厅");
        
        // 创建命令
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
        StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        
        // 创建调用者
        RemoteControl remoteControl = new RemoteControl();
        
        // 设置命令
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, stereoOnWithCD, stereoOff);
        
        // 测试
        System.out.println("------ 测试客厅灯 ------");
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl.undoButtonWasPushed());
        
        System.out.println("\n------ 测试厨房灯 ------");
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        System.out.println(remoteControl.undoButtonWasPushed());
        
        System.out.println("\n------ 测试音响 ------");
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        System.out.println(remoteControl.undoButtonWasPushed());
    }
}

另一个示例 - 宏命令

java 复制代码
// MacroCommand - 宏命令
public class MacroCommand implements Command {
    private Command[] commands;
    
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }
    
    @Override
    public void execute() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].execute();
        }
    }
    
    @Override
    public void undo() {
        for (int i = commands.length - 1; i >= 0; i--) {
            commands[i].undo();
        }
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建接收者
        Light light = new Light("客厅");
        Stereo stereo = new Stereo("客厅");
        TV tv = new TV("客厅");
        
        // 创建命令
        Command lightOn = new LightOnCommand(light);
        Command stereoOn = new StereoOnWithCDCommand(stereo);
        Command tvOn = new TVOnCommand(tv);
        
        Command lightOff = new LightOffCommand(light);
        Command stereoOff = new StereoOffCommand(stereo);
        Command tvOff = new TVOffCommand(tv);
        
        // 创建宏命令
        Command[] partyOn = {lightOn, stereoOn, tvOn};
        Command[] partyOff = {lightOff, stereoOff, tvOff};
        
        MacroCommand partyOnMacro = new MacroCommand(partyOn);
        MacroCommand partyOffMacro = new MacroCommand(partyOff);
        
        // 创建调用者
        RemoteControl remoteControl = new RemoteControl();
        
        // 设置宏命令
        remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
        
        // 测试宏命令
        System.out.println("------ 测试派对模式 ------");
        remoteControl.onButtonWasPushed(0);
        System.out.println();
        remoteControl.offButtonWasPushed(0);
        System.out.println();
        remoteControl.undoButtonWasPushed();
    }
}

优缺点

优点

  1. 降低系统耦合度。将请求调用者和接收者解耦
  2. 新的命令可以很容易地加入到系统中
  3. 可以比较容易地设计一个命令队列或宏命令(组合命令)
  4. 可以方便地实现对请求的Undo和Redo

缺点

  1. 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用

相关模式

  • 组合模式:命令模式可以使用组合模式来实现宏命令
  • 备忘录模式:命令模式可以使用备忘录模式来支持可撤销的操作
  • 策略模式:命令模式和策略模式都封装了算法,但命令模式还封装了调用者与接收者之间的关系

实际应用

  • GUI中的按钮点击事件
  • 文本编辑器中的撤销/重做功能
  • 数据库事务中的回滚操作
  • Java中的Runnable接口
  • Swing中的Action接口
  • 队列和栈中的操作

命令队列

命令模式可以很容易地实现命令队列,将命令对象放入队列中,然后依次执行:

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

public class CommandQueue {
    private Queue<Command> queue = new LinkedList<>();
    
    public void addCommand(Command command) {
        queue.offer(command);
    }
    
    public void executeCommands() {
        while (!queue.isEmpty()) {
            Command command = queue.poll();
            command.execute();
        }
    }
}

注意事项

  1. 命令模式中的命令对象应该保持轻量级,避免包含过多的状态
  2. 命令模式中的撤销操作需要额外的存储空间来保存状态
  3. 命令模式中的命令对象应该是可序列化的,以便于持久化和网络传输
  4. 命令模式中的命令对象应该是线程安全的,特别是在多线程环境中使用时
相关推荐
会员果汁6 天前
19.设计模式-命令模式
设计模式·命令模式
羊群智妍7 天前
2026GEO监测工具精选:免费AI搜索优化监测工具实用指南
百度·微信·命令模式·新浪微博·segmentfault
Engineer邓祥浩10 天前
设计模式学习(16) 23-14 命令模式
学习·设计模式·命令模式
小码过河.12 天前
设计模式——命令模式
设计模式·命令模式
刀法孜然13 天前
23种设计模式 3 行为型模式 之3.7 command 命令模式
设计模式·命令模式
JavaBoy_XJ1 个月前
行为型-命令模式
命令模式
梵豪1 个月前
使用命令模式实现《植物大战僵尸》兵营生产系统
命令模式
stevenzqzq1 个月前
Compose重组的概念1
命令模式·compose
老朱佩琪!1 个月前
Unity命令模式
unity·游戏引擎·命令模式