菜鸟科技网

命令模式实例如何实现与适用?

什么是命令模式?

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

命令模式实例如何实现与适用?-图1
(图片来源网络,侵删)

核心思想: 将“发出请求的对象”(调用者/Invoker)与“接收并执行请求的对象”(接收者/Receiver)解耦,调用者不需要知道接收者的任何细节,它只知道如何调用一个命令对象。


模式核心角色

命令模式主要包含以下几个角色:

  1. Command (命令接口): 定义了执行操作的接口,这个接口通常只包含一个 execute() 方法。
  2. ConcreteCommand (具体命令): 实现了 Command 接口,并持有一个对 Receiver 对象的引用。execute() 方法会调用 Receiver 的相应操作。
  3. Receiver (接收者): 知道如何执行与请求相关的具体业务逻辑,是真正干活的对象。
  4. Invoker (调用者): 要求命令执行请求,它会持有一个 Command 对象的引用,并在某个时刻调用其 execute() 方法,它不关心命令具体是如何实现的。
  5. Client (客户端): 创建具体的 ConcreteCommand 对象,并将其与 ReceiverInvoker 关联起来。

实例场景:智能家居遥控器

假设我们要为一个智能家居系统设计一个遥控器,这个遥控器有几个按钮,可以控制不同的设备(如电灯、风扇、电视)。

  • 请求: 按下遥控器上的按钮。
  • 接收者: 电灯、风扇、电视等具体设备。
  • 调用者: 遥控器上的按钮。

下面我们用代码来实现这个场景。

命令模式实例如何实现与适用?-图2
(图片来源网络,侵删)

步骤 1: 定义 Receiver (接收者)

我们定义具体的设备,它们是真正执行操作的对象。

Light.java (电灯)

// Receiver: 电灯
public class Light {
    public void on() {
        System.out.println("电灯已打开。");
    }
    public void off() {
        System.out.println("电灯已关闭。");
    }
}

Fan.java (风扇)

// Receiver: 风扇
public class Fan {
    public void turnOn() {
        System.out.println("风扇已开启。");
    }
    public void turnOff() {
        System.out.println("风扇已关闭。");
    }
}

步骤 2: 定义 Command 接口 (命令接口)

// Command: 命令接口
public interface Command {
    void execute(); // 执行命令
    void undo();    // 撤销命令 (可选,但很常用)
}

步骤 3: 创建 ConcreteCommand (具体命令)

我们为每个设备的每个操作创建一个具体的命令类,这些命令会持有对应 Receiver 的引用。

命令模式实例如何实现与适用?-图3
(图片来源网络,侵删)

LightOnCommand.java (打开电灯的命令)

// ConcreteCommand: 打开电灯的命令
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(); // 撤销操作就是关闭电灯
    }
}

LightOffCommand.java (关闭电灯的命令)

// ConcreteCommand: 关闭电灯的命令
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();
    }
}

FanOnCommand.java (开启风扇的命令)

// ConcreteCommand: 开启风扇的命令
public class FanOnCommand implements Command {
    private Fan fan;
    public FanOnCommand(Fan fan) {
        this.fan = fan;
    }
    @Override
    public void execute() {
        fan.turnOn();
    }
    @Override
    public void undo() {
        fan.turnOff();
    }
}

步骤 4: 创建 Invoker (调用者)

遥控器就是调用者,它不需要知道是电灯还是风扇,只需要知道它有一个可以按下的 Command

RemoteControl.java (遥控器)

// Invoker: 调用者 - 遥控器
public class RemoteControl {
    private Command command;
    // 设置要执行的命令
    public void setCommand(Command command) {
        this.command = command;
    }
    // 按下按钮,执行命令
    public void buttonWasPressed() {
        command.execute();
    }
    // 添加一个撤销按钮
    public void undoButtonWasPressed() {
        command.undo();
    }
}

步骤 5: 组装 Client (客户端)

main 方法中,我们将所有部分组装起来,让它们协同工作。

SmartHomeDemo.java (客户端)

public class SmartHomeDemo {
    public static void main(String[] args) {
        // 1. 创建接收者
        Light livingRoomLight = new Light();
        Fan bedroomFan = new Fan();
        // 2. 创建具体的命令对象,并设定它的接收者
        Command lightOn = new LightOnCommand(livingRoomLight);
        Command lightOff = new LightOffCommand(livingRoomLight);
        Command fanOn = new FanOnCommand(bedroomFan);
        // 3. 创建调用者(遥控器)
        RemoteControl remote = new RemoteControl();
        System.out.println("--- 场景一:打开客厅的灯 ---");
        remote.setCommand(lightOn); // 设置命令
        remote.buttonWasPressed();  // 按下执行按钮
        System.out.println("\n--- 场景二:打开卧室的风扇 ---");
        remote.setCommand(fanOn); // 设置新的命令
        remote.buttonWasPressed(); // 按下执行按钮
        System.out.println("\n--- 场景三:关闭客厅的灯(撤销操作)---");
        remote.setCommand(lightOff); // 设置关闭灯的命令
        remote.undoButtonWasPressed(); // 按下撤销按钮
        System.out.println("\n--- 场景四:使用宏命令(组合命令)---");
        // 宏命令:一个按钮执行多个操作
        Command partyOnMacro = new MacroCommand(new Command[]{lightOn, fanOn});
        remote.setCommand(partyOnMacro);
        remote.buttonWasPressed();
    }
}

运行结果:

--- 场景一:打开客厅的灯 ---
电灯已打开。
--- 场景二:打开卧室的风扇 ---
风扇已开启。
--- 场景三:关闭客厅的灯(撤销操作)---
电灯已关闭。
--- 场景四:使用宏命令(组合命令) ---
电灯已打开。
风扇已开启。

命令模式的优点

  1. 解耦: InvokerReceiver 完全解耦。Invoker 只知道 Command 接口,不知道具体的 Receiver 是谁,也不知道它如何工作,这使得我们可以轻松地改变 Receiver 或添加新的 Receiver,而无需修改 Invoker 的代码。
  2. 可扩展性: 新增命令非常容易,只需创建一个新的 ConcreteCommand 类并实现 execute() 方法即可,不需要修改现有的 InvokerReceiver 代码,这符合“开闭原则”。
  3. 支持撤销和重做: 通过在 Command 接口中增加 undo() 方法,可以轻松实现撤销操作,历史命令可以被存储在一个堆栈中,实现“重做”功能。
  4. 支持宏命令: 可以将多个命令组合成一个“宏命令”,一次性执行一系列操作,如上面的例子所示,只需创建一个 MacroCommand 即可。
  5. 支持请求排队和日志: 可以将命令对象放入队列中,实现请求的排队执行,也可以将命令对象持久化,实现请求的日志记录,在系统崩溃后可以重新执行这些命令。

适用场景

  • 当你需要将调用操作的对象与知道如何执行该操作的对象解耦时。
  • 当你想要支持撤销操作时。
  • 当你想要将操作组合成宏命令时。
  • 当你需要支持日志记录,并允许在系统崩溃后恢复操作时。
  • 当你想要实现事务性操作时(一组命令要么全部成功,要么全部回滚)。

命令模式通过将请求封装成对象,极大地增强了系统的灵活性、可扩展性和可维护性。

分享:
扫描分享到社交APP
上一篇
下一篇