Java命令模式案例

wen java案例 3

Java命令模式实战案例解析:从理论到落地的完整指南

目录导读

  1. 命令模式核心概念 – 什么是命令模式?解决什么问题?
  2. 模式结构四要素 – 命令、接收者、调用者、客户端的职责划分
  3. Java代码实现案例 – 智能家居遥控器(含完整源码)
  4. 命令模式的扩展应用 – 宏命令、撤销/重做、日志队列
  5. 命令模式 vs 策略模式 – 关键区别与选型建议
  6. 常见面试问答 – 10个高频问题精讲
  7. 性能与最佳实践 – 何时该用,何时慎用

命令模式核心概念

命令模式(Command Pattern) 是一种行为设计模式,它将“请求”封装为独立对象,从而让你能用不同的请求对客户端进行参数化,支持排队、日志记录以及可撤销操作。

Java命令模式案例

核心痛点:当系统需要发送方(Invoker)与执行方(Receiver)完全解耦时,直接调用会导致强耦合、难以扩展,一个按钮需要绑定多种功能,且支持撤销。

生活类比:餐厅点餐 – 顾客(客户端)把订单(命令)给服务员(调用者),服务员只负责传递,厨房(接收者)知道如何做菜,这样“点餐”这个动作可以被记录、排队,甚至可以取消。


模式结构四要素

角色 职责 对应类
Command(命令接口) 声明执行操作的方法 execute() interface Command
ConcreteCommand(具体命令) 绑定接收者,调用接收者的具体方法 LightOnCommand
Receiver(接收者) 真正执行业务逻辑的类 Light
Invoker(调用者/请求发送者) 持有命令对象,触发执行 RemoteControl
Client(客户端) 创建具体命令并设置接收者 MainApp

Java代码实现案例:智能家居遥控器

1 场景描述

实现一个智能家居遥控器,有3个按钮:开灯、关灯、打开电视,要求:

  • 每个按钮可以绑定不同命令
  • 支持“撤销”上一次操作

2 完整源码

// 1. 命令接口
interface Command {
    void execute();
    void undo();
}
// 2. 接收者:电灯
class Light {
    public void on() { System.out.println("灯光已开启"); }
    public void off() { System.out.println("灯光已关闭"); }
}
// 3. 具体命令:开灯命令
class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) { this.light = light; }
    public void execute() { light.on(); }
    public void undo() { light.off(); }
}
// 4. 具体命令:关灯命令
class LightOffCommand implements Command {
    private Light light;
    public LightOffCommand(Light light) { this.light = light; }
    public void execute() { light.off(); }
    public void undo() { light.on(); }
}
// 5. 接收者:电视
class TV {
    public void powerOn() { System.out.println("电视已打开"); }
    public void powerOff() { System.out.println("电视已关闭"); }
}
// 6. 具体命令:开电视
class TVOnCommand implements Command {
    private TV tv;
    public TVOnCommand(TV tv) { this.tv = tv; }
    public void execute() { tv.powerOn(); }
    public void undo() { tv.powerOff(); }
}
// 7. 调用者:遥控器(支持撤销)
class RemoteControl {
    private Command[] commands = new Command[3];
    private Command lastCommand;
    public void setCommand(int slot, Command cmd) {
        commands[slot] = cmd;
    }
    public void pressButton(int slot) {
        if (commands[slot] != null) {
            commands[slot].execute();
            lastCommand = commands[slot];
        }
    }
    public void pressUndo() {
        if (lastCommand != null) {
            lastCommand.undo();
        }
    }
}
// 8. 客户端测试
public class CommandPatternDemo {
    public static void main(String[] args) {
        Light livingRoomLight = new Light();
        TV samsungTV = new TV();
        LightOnCommand lightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand lightOff = new LightOffCommand(livingRoomLight);
        TVOnCommand tvOn = new TVOnCommand(samsungTV);
        RemoteControl remote = new RemoteControl();
        remote.setCommand(0, lightOn);
        remote.setCommand(1, lightOff);
        remote.setCommand(2, tvOn);
        // 按下按钮
        remote.pressButton(0);  // 输出:灯光已开启
        remote.pressButton(2);  // 输出:电视已打开
        remote.pressUndo();     // 输出:电视已关闭(撤销)
    }
}

3 执行结果

灯光已开启
电视已打开
电视已关闭

命令模式的扩展应用

1 宏命令(Composite Command)

将多个命令组合成一个宏命令,一次执行多个操作:

class MacroCommand implements Command {
    private Command[] commands;
    public MacroCommand(Command[] commands) { this.commands = commands; }
    public void execute() {
        for (Command c : commands) c.execute();
    }
    public void undo() {
        for (int i = commands.length - 1; i >= 0; i--) commands[i].undo();
    }
}

2 撤销/重做(多级撤销)

使用栈存储历史命令:

class HistoryManager {
    private Stack<Command> history = new Stack<>();
    public void execute(Command cmd) {
        cmd.execute();
        history.push(cmd);
    }
    public void undoLast() {
        if (!history.isEmpty()) history.pop().undo();
    }
}

3 日志与事务队列

将命令对象序列化到磁盘,实现系统崩溃后的恢复重放,银行转账操作可记录为命令日志。


命令模式 vs 策略模式

对比维度 命令模式 策略模式
目的 将请求封装为对象,解耦发送者与执行者 定义算法族,让算法可互换
关注点 动作的请求、撤销、队列 算法的封装与替换
是否含接收者 有独立的Receiver 算法本身即接收者
典型应用 遥控器、编辑器撤销、线程池任务 排序算法、支付方式选择

选型建议:如果需要延迟执行、撤销、记录日志,用命令模式;如果只是算法替换,用策略模式更轻量。


常见面试问答(10个高频问题)

Q1:命令模式的主要优点是什么?

① 发送者与接收者完全解耦;② 支持撤销/重做;③ 支持宏命令和队列;④ 易于扩展新命令,符合开闭原则。

Q2:命令模式有哪些缺点?

类数量会增加(每增加一个命令就增加一个类),可能导致系统类膨胀。

Q3:execute() 方法中能否抛出异常?

建议不抛出,因为调用者无法预知异常处理方式,可在命令内自行捕获或转换为运行时异常。

Q4:如何实现“多级撤销”?

使用两个Stack(撤销栈和重做栈),每次执行后压入撤销栈。

Q5:命令模式在JDK中的应用?

Runnable接口就是命令模式(Thread + Runnable),javax.swing.Action 也是。

Q6:命令队列的应用场景?

任务调度系统、消息队列(如订单处理流水线)、游戏中的动作回放。

Q7:客户端和调用者的区别?

客户端负责创建命令对象(setReceiver),调用者只负责持有并触发命令,不关心命令具体实现。

Q8:命令对象是否可以被序列化?

可以,但需保证其中引用的Receiver也可序列化,否则需要改为存储方法名+参数。

Q9:能否用Lambda简化命令模式?

可以!如果Command接口是函数式接口(只有一个抽象方法),可用Lambda表达式直接传方法引用,但注意Lambda无法轻松实现undo,需额外设计。

Q10:命令模式与工厂模式如何结合?

工厂模式负责创建命令对象,让客户端无需知道具体命令类名,实现“命令族”的集中管理。


性能与最佳实践

✅ 推荐使用场景

  • 需要参数化请求(如按钮、菜单项)
  • 需要支持撤销/重做(编辑器、绘图工具)
  • 需要记录变更日志(数据库事务、文件系统快照)
  • 需要事务性操作(全部成功或全部回滚)

⚠️ 注意事项

  1. 不要滥用:简单一对一调用不必使用命令模式,否则增加代码复杂度。
  2. 命令对象应轻量:避免在命令中持有大量状态,可考虑引用共享的Receiver。
  3. Undo/Redo设计:优先使用Stack,并注意内存消耗(大操作可限制栈深度)。

命令模式是Java开发中最优雅的解耦工具之一,尤其适合需要“可撤销”“可排队”“可日志”的业务场景,通过本文的智能家居案例,你已经掌握了命令模式的核心结构、扩展技巧以及面试要点。命令模式的核心价值在于将“行为”变成“数据”,让系统行为可以被存储、传递和回放。

你可以在自己的项目里尝试引入命令模式,从“撤销功能”或“任务队列”开始,体验它带来的灵活性和可维护性!

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