Java案例中的命令模式怎么用?

wen java案例 2

Java案例中的命令模式怎么用?——从原理到实战的完整指南

📑 目录导读

  1. 命令模式的核心概念与适用场景
  2. Java实现命令模式的四大核心角色
  3. 经典案例:智能家居遥控系统(附完整代码)
  4. 命令模式的高级技巧:撤销、队列与宏命令
  5. 常见面试问答(Q&A)
  6. 何时选择命令模式?

命令模式的核心概念与适用场景

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

Java案例中的命令模式怎么用?

核心思想:将“调用操作的对象”与“知道如何执行操作的对象”解耦,在Java开发中,当遇到以下场景时,命令模式尤为适用:

  • 需要将请求发送者和接收者解耦(如GUI按钮与业务逻辑分离)
  • 支持操作的撤销/重做(如文本编辑器的Ctrl+Z)
  • 需要实现请求的排队、日志记录或异步执行(如任务调度系统)
  • 需要组合多个操作为宏命令(如智能家居的场景模式)

Java实现命令模式的四大核心角色

在Java中实现命令模式,需要清晰理解以下四个角色:

角色 作用 类比
Command(抽象命令) 声明执行操作的接口 遥控器上的“按钮”
ConcreteCommand(具体命令) 绑定Receiver与动作 按下“开灯”按钮的实际电路
Invoker(调用者/请求者) 持有命令对象并触发执行 遥控器本体
Receiver(接收者) 真正执行具体业务逻辑 灯泡、空调等设备

关键区别:与传统直接调用的不同在于——调用者(Invoker)不知道接收者(Receiver)是谁,它只知道调用 command.execute() 方法。


经典案例:智能家居遥控系统(完整Java代码)

场景描述

我们需要为一套智能家居设计遥控器,支持开灯、关灯、打开空调、关闭空调,并且要求未来能轻松添加新设备(如电视、窗帘)而不修改现有代码。

定义Receiver(设备类)

// 灯
public class Light {
    public void on() { System.out.println("灯已打开"); }
    public void off() { System.out.println("灯已关闭"); }
}
// 空调
public class AirConditioner {
    public void start() { System.out.println("空调启动"); }
    public void stop() { System.out.println("空调关闭"); }
}

定义Command接口

public interface Command {
    void execute();
    // 可以增加undo()方法支持撤销
    void undo();
}

实现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();
    }
}
public class AirConditionerStartCommand implements Command {
    private AirConditioner ac;
    public AirConditionerStartCommand(AirConditioner ac) {
        this.ac = ac;
    }
    @Override
    public void execute() {
        ac.start();
    }
    @Override
    public void undo() {
        ac.stop();
    }
}

实现Invoker(遥控器)

public class RemoteControl {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command lastCommand;  // 用于撤销
    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;
        }
        lastCommand = noCommand;
    }
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
    // 按下“开”按钮
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        lastCommand = onCommands[slot];
    }
    // 按下“关”按钮
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
        lastCommand = offCommands[slot];
    }
    // 撤销操作
    public void undoButtonWasPushed() {
        lastCommand.undo();
    }
}

客户端使用

public class Client {
    public static void main(String[] args) {
        // 创建接收者
        Light livingRoomLight = new Light();
        AirConditioner bedroomAC = new AirConditioner();
        // 创建命令
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        ACStartCommand bedroomACStart = new ACStartCommand(bedroomAC);
        ACStopCommand bedroomACStop = new ACStopCommand(bedroomAC);
        // 设置遥控器
        RemoteControl remote = new RemoteControl();
        remote.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remote.setCommand(1, bedroomACStart, bedroomACStop);
        // 执行操作
        remote.onButtonWasPushed(0);  // 开灯
        remote.onButtonWasPushed(1);  // 启动空调
        remote.offButtonWasPushed(0); // 关灯
        remote.undoButtonWasPushed(); // 撤销关灯(灯重新打开)
    }
}

命令模式的高级技巧

1 支持撤销(Undo)操作

关键在于每个ConcreteCommand保存执行前的状态,并在undo()中恢复,对于状态复杂的操作,可以使用备忘录模式配合。

2 实现命令队列

使用Queue<Command>存放命令,由异步线程池逐条执行,实现批处理或任务调度:

Queue<Command> commandQueue = new LinkedList<>();
commandQueue.add(new LightOnCommand(livingRoomLight));
commandQueue.add(new ACStartCommand(bedroomAC));
while (!commandQueue.isEmpty()) {
    commandQueue.poll().execute();
}

3 宏命令(复合命令)

组合多个命令为一个宏命令:

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();
        }
    }
    @Override
    public void undo() {
        for (int i = commands.length - 1; i >= 0; i--) {
            commands[i].undo();
        }
    }
}

常见面试问答(Q&A)

Q1:命令模式与策略模式的区别是什么?
A:两者都涉及接口和实现分离,但意图不同:策略模式用于封装算法族(如不同的加密算法),强调“怎么做”;命令模式用于封装请求(如按钮点击),强调“做什么”,且命令模式通常包含Receiver对象,而策略模式不强制。

Q2:命令模式如何处理线程安全问题?
A:如果多个线程共享Invoker,需确保Command对象是无状态的,或使用同步块,更常见的做法是为每个线程分配独立的Invoker实例(如每个HTTP请求对应的Servlet)。

Q3:Java中是否有现成的命令模式实现?
A:java.lang.Runnable 可视为命令模式的变体(Runnable充当Command,execute为run())。javax.swing.ActionUndoManager 也是官方实现例子。

Q4:命令模式可以替代Switch语句吗?
A:完全可以!将每个case分支封装为具体命令对象,通过Map<条件, Command>将条件与命令映射,消除复杂的switch,符合开闭原则。


何时选择命令模式?

适合场景 不适合场景
需要将调用者和实现者解耦 只有简单的直接调用(过度设计)
需要记录、撤销、重做操作 对响应时间要求极高(增加对象开销)
需要组合操作为宏命令 命令对象本身非常复杂且状态多变
需要排队执行或异步调用 项目规模很小且不会扩展

最佳实践建议:在业务中,优先考虑是否真的需要“撤销”功能;如果仅是简单的请求封装,使用Java 8的FunctionRunnable即可完成任务,无需引入命令模式增加复杂度。


本文基于实际Java项目案例编写,参考了《Head First设计模式》与多个开源项目代码逻辑,遵循SEO优化原则,确保内容原创性与实用性,任何转载请联系作者。

上一篇如何用Java案例实现数据校验?

下一篇当前分类已是最新一篇

抱歉,评论功能暂时关闭!