1. 命令模式:解耦请求与执行的利器
第一次接触命令模式是在重构一个电商订单系统时。当时系统里有几十种订单操作(创建、支付、取消、退货等),每个操作都直接调用了业务逻辑层的对应方法。随着业务增长,代码变成了意大利面条式的结构——添加新功能要改N个地方,回滚操作像走钢丝,更别提实现操作日志和撤销功能了。直到发现命令模式,才明白什么是"用抽象解耦具体"的真谛。
命令模式(Command Pattern)属于行为型设计模式,它将请求封装成独立对象,让你能参数化客户端的不同请求、队列化请求、记录请求日志,以及支持可撤销操作。就像餐厅点餐时服务员写的订单小票——厨师不需要知道是谁点的菜,只需要按照小票执行;我们也可以随时加菜(新增命令)或退菜(撤销命令)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模式结构与核心思想
2.1 UML类图解析
code复制[客户端] → [Invoker]
↑
[Command] (接口)
↑
[ConcreteCommand] → [Receiver]
- Command:声明执行操作的接口,通常包含
execute()和undo()方法 - ConcreteCommand:将接收者对象绑定到动作,实现execute方法
- Client:创建具体命令对象并设置接收者
- Invoker:要求命令执行请求
- Receiver:知道如何实施请求的具体操作
2.2 生活化类比
想象智能家居的万能遥控器:
- 每个按钮(Invoker)绑定一个命令对象(Command)
- 命令对象内部关联着具体设备(Receiver)
- 按下按钮时,遥控器不关心具体操作,只触发命令执行
- 新增设备只需新增命令类,无需修改遥控器代码
3. 实战:电商订单系统改造
3.1 原始问题代码示例
java复制// 直接调用业务逻辑的典型反模式
public class OrderService {
public void createOrder() { /* 直接调用库存、支付等服务 */ }
public void cancelOrder() { /* 分散在各处的取消逻辑 */ }
// 其他数十个类似方法...
}
3.2 命令模式改造方案
java复制// 命令接口
public interface OrderCommand {
void execute();
void undo();
}
// 具体命令
public class CreateOrderCommand implements OrderCommand {
private OrderReceiver receiver;
public CreateOrderCommand(OrderReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.createOrder();
}
@Override
public void undo() {
receiver.rollbackCreate();
}
}
// 接收者
public class OrderReceiver {
public void createOrder() { /* 真正的业务逻辑 */ }
public void rollbackCreate() { /* 回滚逻辑 */ }
}
// 调用者
public class OrderInvoker {
private List<OrderCommand> history = new ArrayList<>();
public void executeCommand(OrderCommand cmd) {
cmd.execute();
history.add(cmd);
}
public void undoLastCommand() {
if (!history.isEmpty()) {
OrderCommand cmd = history.remove(history.size()-1);
cmd.undo();
}
}
}
3.3 关键改造点说明
- 解耦调用与执行:Invoker只认识Command接口,不依赖具体实现
- 支持撤销机制:通过维护历史命令列表实现
- 易于扩展:新增操作只需添加新的Command类
- 支持复合命令:可以创建宏命令(一组命令的组合)
4. 高级应用场景
4.1 事务处理
java复制// 复合命令实现事务
public class TransactionCommand implements OrderCommand {
private List<OrderCommand> commands = new ArrayList<>();
public void addCommand(OrderCommand cmd) {
commands.add(cmd);
}
@Override
public void execute() {
for (OrderCommand cmd : commands) {
cmd.execute();
}
}
@Override
public void undo() {
// 逆序执行undo
for (int i=commands.size()-1; i>=0; i--) {
commands.get(i).undo();
}
}
}
4.2 异步命令队列
java复制// 线程池执行命令
public class CommandQueue {
private ExecutorService executor = Executors.newFixedThreadPool(4);
private Queue<Command> queue = new ConcurrentLinkedQueue<>();
public void addCommand(Command cmd) {
queue.offer(cmd);
}
public void processCommands() {
while (!queue.isEmpty()) {
Command cmd = queue.poll();
executor.submit(() -> cmd.execute());
}
}
}
4.3 日志与持久化
java复制// 可序列化的命令
public abstract class PersistentCommand implements Serializable, Command {
protected Date timestamp;
public void execute() {
this.timestamp = new Date();
doExecute();
saveToLog();
}
protected abstract void doExecute();
private void saveToLog() {
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("command.log", true))) {
out.writeObject(this);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 模式变体与相关模式
5.1 变体:无接收者的命令
java复制// 自包含命令(Lambda实现)
@FunctionalInterface
public interface SimpleCommand {
void execute();
}
// 使用示例
SimpleCommand cmd = () -> System.out.println("Hello Command!");
cmd.execute();
5.2 与其它模式的关系
- 组合模式:用于实现宏命令
- 备忘录模式:辅助实现撤销功能
- 责任链模式:可以将命令组织成处理链
- 策略模式:都是封装算法,但命令模式更关注调用过程
6. 实战中的坑与经验
6.1 性能考量
警告:在需要高频创建命令的场景(如游戏循环),要避免命令对象GC开销
解决方案:
- 使用对象池复用命令实例
- 对于无状态命令,设计成单例
- 考虑使用轻量级的函数式接口替代
6.2 线程安全
- 共享接收者时要注意同步
- 命令历史记录建议使用
ConcurrentLinkedQueue - 撤销操作要考虑重入锁
6.3 典型误用场景
- 过度设计:简单请求直接调用即可
- 生命期管理不当:长时间持有命令引用导致内存泄漏
- 忽略异常处理:命令执行失败时的回滚策略要明确
7. 现代语言中的演进
7.1 Java的函数式支持
java复制// 使用Lambda简化命令模式
invoker.executeCommand(() -> receiver.action());
7.2 C#的委托机制
csharp复制// 委托本身就是命令模式的实现
public delegate void CommandDelegate();
// 使用示例
CommandDelegate cmd = receiver.Method;
cmd.Invoke();
7.3 JavaScript的事件模型
javascript复制// DOM事件就是典型的命令模式应用
button.addEventListener('click', () => {
// 命令逻辑
});
8. 设计权衡与替代方案
8.1 何时使用命令模式
- 需要回调机制(如GUI事件处理)
- 需要实现撤销/重做功能
- 需要支持事务操作
- 需要将操作排队或记录日志
8.2 何时避免使用
- 请求本身非常简单直接
- 性能敏感的底层代码
- 不需要解耦调用者和接收者
8.3 替代方案比较
| 方案 | 优点 | 缺点 |
|---|---|---|
| 直接调用 | 简单直接 | 耦合度高 |
| 策略模式 | 算法可替换 | 不支持撤销 |
| 命令模式 | 完全解耦 | 类数量增加 |
9. 测试策略
9.1 单元测试要点
java复制@Test
public void testCommandExecution() {
// 准备
Receiver mockReceiver = mock(Receiver.class);
Command cmd = new ConcreteCommand(mockReceiver);
// 执行
cmd.execute();
// 验证
verify(mockReceiver).action();
}
@Test
public void testUndo() {
// 准备
ReceiverSpy receiver = new ReceiverSpy();
Command cmd = new ConcreteCommand(receiver);
// 执行
cmd.execute();
cmd.undo();
// 验证
assertTrue(receiver.executed);
assertTrue(receiver.undone);
}
9.2 集成测试场景
- 测试命令序列的正确执行顺序
- 测试长时间运行的命令超时处理
- 测试命令持久化与恢复
10. 真实案例:智能家居控制中心
最后分享一个我参与的智能家居项目中的命令模式实现:
python复制class SmartHomeCommand(ABC):
@abstractmethod
def execute(self): pass
@abstractmethod
def undo(self): pass
class LightOnCommand(SmartHomeCommand):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class SmartHomeApp:
def __init__(self):
self._history = []
def execute_command(self, command):
command.execute()
self._history.append(command)
def undo_last(self):
if self._history:
self._history.pop().undo()
# 使用示例
app = SmartHomeApp()
living_light = Light()
cmd = LightOnCommand(living_light)
app.execute_command(cmd) # 开灯
app.undo_last() # 关灯
这个实现后来扩展支持了:
- 语音命令(通过额外适配层)
- 场景模式(宏命令)
- 远程控制(命令序列化)
- 执行日志(持久化命令)
命令模式特别适合这种"一个操作影响多个设备"的场景。比如"晚安模式"要同时关灯、拉窗帘、调温度,用命令模式可以原子性地执行和撤销这组操作。
