文章概述
命令模式(Command Pattern)是GoF行为型设计模式中的经典之作,其核心定义是:将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化、对请求排队或记录请求日志,以及支持可撤销的操作。这一模式的核心意图在于将“请求的发起者”与“请求的执行者”解耦,让调用者无需关心接收者的具体实现,只需持有命令对象并调用其统一接口。
命令模式解决的三大核心痛点极具工程价值:其一,请求调用者与执行者的彻底解耦,使得UI按钮、菜单项等组件无需直接依赖业务逻辑实现类;其二,请求的排队与延迟执行,为任务调度、消息队列、命令缓冲提供了优雅的抽象;其三,操作的撤销与重做,通过在命令对象中封装状态快照,轻松实现无限级回退功能。
本文将沿着一条由浅入深的知识脉络展开:首先从Java原生回调机制(Runnable)切入,阐释命令对象封装的原始形态;接着构建经典命令模式的五元组模型,并延伸至宏命令、撤销队列等进阶特性;随后深入JDK、Spring、MyBatis、Netty、Dubbo等主流框架源码,剖析命令模式的工业级落地;进一步将视野拓展至分布式环境,探讨命令模式在任务调度、消息队列、Saga分布式事务中的关键作用;最后通过五大实战场景的完整Demo与十余道专家面试题,助你彻底打通命令模式的任督二脉。
一、模式定义与结构
1.1 GoF标准定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而让你可以使用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
1.2 UML类图
classDiagram
class Command {
<<interface>>
+execute() void
+undo() void
}
class ConcreteCommand {
-receiver: Receiver
-state: Object
+execute() void
+undo() void
}
class Receiver {
+action() void
+reverseAction() void
}
class Invoker {
-command: Command
+setCommand(Command) void
+invoke() void
}
class Client {
+main() void
}
Command <|.. ConcreteCommand
ConcreteCommand --> Receiver
Invoker --> Command
Client ..> ConcreteCommand
Client ..> Receiver
Client ..> Invoker
1.3 角色职责与生命周期详述
上述类图严格遵循GoF命令模式的经典结构,五个核心角色各司其职:
Command(命令接口):声明执行操作的统一契约,通常包含execute()方法,在支持撤销的场景下还需声明undo()方法。该接口是整个模式的核心抽象层,保证了调用者与具体命令实现的隔离。
ConcreteCommand(具体命令类):实现Command接口,内部持有一个Receiver对象的引用,并封装了针对该Receiver的具体操作。在execute()方法中,它负责调用Receiver的相应业务方法;若支持撤销,则需在undo()中调用Receiver的反向操作,或者自行恢复所保存的状态快照。命令对象本身是“行为”的物化,因此往往是无状态的(除必要参数外),可以在不同时刻被多次执行。
Receiver(接收者):真正执行业务逻辑的类。它知道如何完成与请求相关的具体操作,例如电灯的开与关、文档的插入与删除。接收者完全独立于命令对象,可以被多个不同的命令复用。
Invoker(调用者):持有命令对象的引用,并在适当的时机(如按钮点击、定时触发、消息到达)调用命令的execute()方法。调用者不关心命令的具体实现,也不知道接收者的存在,它仅仅是一个命令的“触发器”。
Client(客户端):负责创建具体命令对象,并将其与对应的接收者绑定,最后将命令对象注入到调用者中。在Java应用中,客户端通常体现为配置代码、工厂类或Spring容器的Bean组装过程。
命令对象的生命周期:始于客户端通过构造函数或Setter方法将Receiver注入ConcreteCommand;随后该命令对象被传递给Invoker持有;当用户触发Invoker的调用动作时,Invoker回调命令的execute()方法;命令内部再将调用委托给Receiver执行具体业务;执行完毕后,命令对象可被丢弃,也可被存入历史列表以备撤销。整个过程宛如一封精心设计的“委托书”,将动作的请求者与执行者完美解耦。
二、代码演进与实现
2.1 不使用模式的原始代码:紧耦合之痛
设想一个简单的智能家居遥控器场景:遥控器上有一个按钮,按下后打开电灯。最直接的实现如下:
// 接收者:电灯
class Light {
public void turnOn() {
System.out.println("电灯打开了");
}
public void turnOff() {
System.out.println("电灯关闭了");
}
}
// 调用者:遥控器
class RemoteControl {
private Light light;
public RemoteControl(Light light) {
this.light = light;
}
public void pressButton() {
light.turnOn(); // 直接依赖Light的具体方法
}
}
// 客户端
public class NoPatternDemo {
public static void main(String[] args) {
Light light = new Light();
RemoteControl remote = new RemoteControl(light);
remote.pressButton(); // 输出:电灯打开了
}
}
问题分析:
- 高耦合:
RemoteControl直接依赖Light类,若需控制电视、空调等其他设备,必须修改遥控器代码。 - 扩展困难:无法支持撤销操作、宏命令(一键执行多个操作)或命令日志记录。
- 难以测试:单元测试时无法方便地替换为Mock对象。
2.2 经典命令模式重构
步骤1:定义Command接口
/**
* 命令接口,声明执行与撤销方法
*/
public interface Command {
void execute(); // 执行命令
void undo(); // 撤销命令
}
步骤2:实现具体命令类
/**
* 开灯命令:绑定Light接收者,实现开灯操作
*/
public class LightOnCommand implements Command {
private Light light; // 持有接收者引用
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn(); // 委托接收者执行
}
@Override
public void undo() {
light.turnOff(); // 撤销即执行反向操作
}
}
/**
* 关灯命令
*/
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
@Override
public void undo() {
light.turnOn();
}
}
步骤3:增强Receiver类
/**
* 电灯接收者,提供具体的业务操作
*/
public class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void turnOn() {
System.out.println(location + " 电灯已打开");
}
public void turnOff() {
System.out.println(location + " 电灯已关闭");
}
}
步骤4:定义Invoker类
import java.util.Stack;
/**
* 遥控器调用者:可设置多个插槽的命令,并支持撤销
*/
public class RemoteControl {
private Command[] onCommands; // 开启命令数组
private Command[] offCommands; // 关闭命令数组
private Stack<Command> executedCommands; // 已执行命令栈,用于撤销
public RemoteControl(int slots) {
onCommands = new Command[slots];
offCommands = new Command[slots];
executedCommands = new Stack<>();
// 初始化空命令(避免null判断)
Command noCommand = new NoCommand();
for (int i = 0; i < slots; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void pressOnButton(int slot) {
Command cmd = onCommands[slot];
cmd.execute();
executedCommands.push(cmd); // 记录已执行命令
}
public void pressOffButton(int slot) {
Command cmd = offCommands[slot];
cmd.execute();
executedCommands.push(cmd);
}
public void pressUndoButton() {
if (!executedCommands.isEmpty()) {
Command lastCmd = executedCommands.pop();
lastCmd.undo(); // 执行撤销
} else {
System.out.println("没有可撤销的命令");
}
}
}
/**
* 空命令(Null Object模式),避免空指针检查
*/
class NoCommand implements Command {
@Override
public void execute() { /* 什么也不做 */ }
@Override
public void undo() { /* 什么也不做 */ }
}
步骤5:客户端组装
public class CommandPatternDemo {
public static void main(String[] args) {
// 创建接收者
Light livingRoomLight = new Light("客厅");
Light kitchenLight = new Light("厨房");
// 创建具体命令
Command livingRoomLightOn = new LightOnCommand(livingRoomLight);
Command livingRoomLightOff = new LightOffCommand(livingRoomLight);
Command kitchenLightOn = new LightOnCommand(kitchenLight);
Command kitchenLightOff = new LightOffCommand(kitchenLight);
// 创建调用者并配置命令
RemoteControl remote = new RemoteControl(2);
remote.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remote.setCommand(1, kitchenLightOn, kitchenLightOff);
// 模拟用户操作
System.out.println("----- 测试开灯 -----");
remote.pressOnButton(0); // 客厅灯开
remote.pressOnButton(1); // 厨房灯开
System.out.println("\n----- 测试关灯 -----");
remote.pressOffButton(0); // 客厅灯关
System.out.println("\n----- 测试撤销 -----");
remote.pressUndoButton(); // 撤销关客厅灯 -> 客厅灯开
remote.pressUndoButton(); // 撤销开厨房灯 -> 厨房灯关
}
}
2.3 命令模式的进阶特性
2.3.1 宏命令(MacroCommand)
宏命令将多个命令组合成一个复合命令,实现一键批量执行。
import java.util.ArrayList;
import java.util.List;
/**
* 宏命令:组合多个命令,批量执行与撤销
*/
public class MacroCommand implements Command {
private List<Command> commands = new ArrayList<>();
public void addCommand(Command cmd) {
commands.add(cmd);
}
@Override
public void execute() {
for (Command cmd : commands) {
cmd.execute();
}
}
@Override
public void undo() {
// 撤销时通常按相反顺序执行
for (int i = commands.size() - 1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
// 客户端使用示例
public class MacroCommandDemo {
public static void main(String[] args) {
Light light = new Light("卧室");
LightOnCommand lightOn = new LightOnCommand(light);
LightOffCommand lightOff = new LightOffCommand(light);
// 创建宏命令:一键开关灯
MacroCommand partyMode = new MacroCommand();
partyMode.addCommand(lightOn);
partyMode.addCommand(lightOff);
partyMode.addCommand(lightOn); // 闪烁效果
System.out.println("执行派对模式宏命令:");
partyMode.execute();
System.out.println("\n撤销派对模式:");
partyMode.undo();
}
}
2.3.2 撤销操作的增强实现
对于需要保存状态的复杂撤销场景(如文本编辑),命令对象需自行保存执行前的状态快照。
/**
* 文档编辑器接收者
*/
class Document {
private StringBuilder content = new StringBuilder();
public void insert(int position, String text) {
content.insert(position, text);
}
public void delete(int position, int length) {
content.delete(position, position + length);
}
public String getContent() {
return content.toString();
}
}
/**
* 插入命令:保存插入的位置和文本,以便撤销时删除
*/
class InsertCommand implements Command {
private Document doc;
private int position;
private String text;
public InsertCommand(Document doc, int position, String text) {
this.doc = doc;
this.position = position;
this.text = text;
}
@Override
public void execute() {
doc.insert(position, text);
}
@Override
public void undo() {
doc.delete(position, text.length());
}
}
2.3.3 命令队列与延迟执行
Invoker可以维护一个命令队列,支持异步执行或定时调度。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 命令队列处理器:异步消费命令
*/
public class CommandQueueInvoker {
private BlockingQueue<Command> queue = new LinkedBlockingQueue<>();
private volatile boolean running = true;
public CommandQueueInvoker() {
// 启动消费者线程
new Thread(this::consume).start();
}
public void submit(Command cmd) {
queue.offer(cmd);
System.out.println("命令已入队: " + cmd.getClass().getSimpleName());
}
private void consume() {
while (running) {
try {
Command cmd = queue.take(); // 阻塞获取
System.out.println("执行队列中的命令: " + cmd.getClass().getSimpleName());
cmd.execute();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
public void shutdown() {
running = false;
}
}
// 使用示例
public class QueueDemo {
public static void main(String[] args) throws InterruptedException {
Light light = new Light("走廊");
CommandQueueInvoker invoker = new CommandQueueInvoker();
invoker.submit(new LightOnCommand(light));
invoker.submit(new LightOffCommand(light));
invoker.submit(new LightOnCommand(light));
Thread.sleep(100); // 等待异步执行完成
invoker.shutdown();
}
}
2.4 命令模式执行时序图
sequenceDiagram
participant Client
participant Invoker
participant Command
participant Receiver
Client->>Invoker: setCommand(command)
Client->>Invoker: pressButton()
Invoker->>Command: execute()
Command->>Receiver: action()
Receiver-->>Command: 执行完成
Command-->>Invoker: 返回
Invoker->>Invoker: 记录命令至历史栈
Note over Client,Receiver: 后续撤销操作
Client->>Invoker: pressUndoButton()
Invoker->>Command: undo()
Command->>Receiver: reverseAction()
Receiver-->>Command: 撤销完成
Command-->>Invoker: 返回
时序图说明:上图完整刻画了命令模式的一次请求-响应-撤销生命周期。客户端首先将封装好的命令对象注入调用者(遥控器);当用户触发按钮时,调用者直接回调命令对象的execute()方法;命令对象再将调用委派给接收者执行具体业务逻辑;执行完毕后,调用者将命令对象压入历史栈以备撤销。当用户点击撤销按钮时,调用者从栈中弹出最近执行的命令并调用其undo()方法,命令对象则调用接收者的反向操作恢复状态。整个过程调用者与接收者之间完全解耦,所有交互均通过命令对象中转,从而实现了高度的灵活性与可扩展性。这一机制在GUI应用、事务管理、宏录制等领域具有不可替代的价值。
三、源码级应用分析
3.1 JDK中的命令模式
3.1.1 java.lang.Runnable
Runnable是JDK中最纯粹的Command接口,它将“可执行的任务”抽象为对象,而Thread则充当Invoker角色。
// Runnable即命令接口
@FunctionalInterface
public interface Runnable {
void run(); // 对应Command的execute()
}
// Thread是典型的Invoker
public class Thread implements Runnable {
private Runnable target; // 持有命令对象
public Thread(Runnable target) {
this.target = target;
}
@Override
public void run() {
if (target != null) {
target.run(); // 触发命令执行
}
}
public synchronized void start() {
// 启动新线程,最终调用run()
}
}
// 客户端代码
new Thread(() -> System.out.println("任务执行")).start();
3.1.2 javax.swing.Action
Swing中的Action接口扩展了ActionListener,不仅封装了行为,还携带了名称、图标等UI属性,并支持状态管理(enabled/disabled)。
public interface Action extends ActionListener {
void actionPerformed(ActionEvent e); // 命令执行方法
void setEnabled(boolean b);
boolean isEnabled();
// 可携带属性:NAME, SMALL_ICON, SHORT_DESCRIPTION等
}
// JButton作为Invoker
JButton button = new JButton(new AbstractAction("复制") {
@Override
public void actionPerformed(ActionEvent e) {
// 具体命令逻辑
textComponent.copy();
}
});
3.1.3 Callable与FutureTask
Callable是支持返回值和异常的命令接口,FutureTask则充当了命令与Invoker之间的适配器。
// Callable:带返回值的命令
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
// FutureTask实现了Runnable,将Callable适配为Runnable
public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable;
public FutureTask(Callable<V> callable) {
this.callable = callable;
}
@Override
public void run() {
// 执行callable.call()并保存结果
}
}
// 线程池作为Invoker
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() -> "执行结果");
3.2 Spring框架中的命令模式
3.2.1 CommandLineRunner与ApplicationRunner
Spring Boot启动后执行的一组回调接口,本质上是命令模式的应用。
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception; // 命令执行方法
}
// SpringApplication作为Invoker
public class SpringApplication {
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<CommandLineRunner> runners = context.getBeansOfType(CommandLineRunner.class);
for (CommandLineRunner runner : runners) {
runner.run(args.getSourceArgs()); // 依次执行命令
}
}
}
3.2.2 @EventListener与事件机制
Spring的事件机制中,ApplicationEvent可视为命令对象,ApplicationListener作为Receiver,ApplicationContext则是Invoker。
// 事件即命令对象
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
// ...
}
// 监听器即接收者
@Component
public class OrderEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 执行业务逻辑
}
}
// 发布者即调用者
@Autowired
private ApplicationEventPublisher publisher;
publisher.publishEvent(new OrderCreatedEvent(order));
3.2.3 Spring MVC的HandlerAdapter
HandlerAdapter将HTTP请求适配为具体的Controller方法调用,充分体现了命令模式的变体。
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
}
// DispatcherServlet作为Invoker
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
HandlerExecutionChain mappedHandler = getHandler(request);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
}
3.3 MyBatis中的Executor
MyBatis的Executor接口定义了数据库操作的命令契约,各实现类为具体命令。
public interface Executor {
<E> List<E> query(MappedStatement ms, Object parameter) throws SQLException;
int update(MappedStatement ms, Object parameter) throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
// ...
}
// 具体命令实现
public class SimpleExecutor extends BaseExecutor { /* ... */ }
public class BatchExecutor extends BaseExecutor { /* ... */ }
public class ReuseExecutor extends BaseExecutor { /* ... */ }
// Plugin机制使用动态代理对Executor进行命令包装
public class Plugin implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 拦截Executor方法,实现插件增强
}
}
3.4 Netty中的ChannelOutboundInvoker
Netty将出站网络操作封装为命令,Channel作为Invoker。
public interface ChannelOutboundInvoker {
ChannelFuture bind(SocketAddress localAddress);
ChannelFuture connect(SocketAddress remoteAddress);
ChannelFuture write(Object msg);
ChannelFuture close();
// ...
}
// 具体命令由ChannelHandlerContext触发
ctx.writeAndFlush(msg); // 写入消息命令
3.5 Dubbo的Invoker接口
Dubbo的核心接口Invoker正是命令模式的完美体现,将RPC调用封装为命令对象。
public interface Invoker<T> {
Class<T> getInterface();
Result invoke(Invocation invocation) throws RpcException; // 执行RPC调用
}
// Protocol作为Invoker的工厂
public interface Protocol {
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}
// Filter链对Invoker进行包装增强
public class FilterChain {
public Result invoke(Invoker<?> invoker, Invocation invocation) {
// 执行过滤器链,最后调用invoker.invoke()
}
}
四、分布式环境下的命令模式
命令模式在分布式系统中大放异彩,它天然支持命令的序列化、网络传输、异步处理与补偿机制。
4.1 分布式任务调度中的命令封装
在XXL-Job、ElasticJob等分布式调度框架中,JobHandler即为命令对象,调度中心扮演Invoker。
// XXL-Job中的命令抽象
public abstract class IJobHandler {
public abstract ReturnT<String> execute(String param) throws Exception;
}
// 调度器ExecutorBiz作为Invoker
public interface ExecutorBiz {
ReturnT<String> run(TriggerParam triggerParam);
}
4.2 消息队列中的命令对象
将Command序列化后通过RabbitMQ/Kafka投递,消费者反序列化并执行。
// 命令基类
public abstract class BaseCommand implements Serializable {
private String commandId;
private long timestamp;
public abstract void execute();
}
// 具体命令:下单命令
public class CreateOrderCommand extends BaseCommand {
private Long userId;
private List<OrderItem> items;
@Override
public void execute() {
// 执行业务逻辑
System.out.println("创建订单:" + userId);
}
}
// 生产者
public class CommandProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendCommand(BaseCommand command) {
String json = JSON.toJSONString(command);
rabbitTemplate.convertAndSend("command.exchange", "command.route", json);
}
}
// 消费者
@Component
public class CommandConsumer {
@RabbitListener(queues = "command.queue")
public void handleCommand(String message) {
BaseCommand cmd = JSON.parseObject(message, BaseCommand.class);
cmd.execute(); // 执行命令
}
}
4.3 分布式事务中的命令模式:Saga模式
在Seata的Saga模式中,每个事务参与者都被封装为一个命令,包含正向执行与反向补偿。
// Saga命令接口
public interface SagaCommand {
void execute(); // 正向操作
void compensate(); // 补偿操作
}
// 具体命令:扣减库存
public class DeductStockCommand implements SagaCommand {
@Override
public void execute() {
// 扣减库存SQL
}
@Override
public void compensate() {
// 补偿:恢复库存
}
}
// Saga协调器作为Invoker
public class SagaCoordinator {
private Stack<SagaCommand> executedCommands = new Stack<>();
public void execute(SagaCommand cmd) {
try {
cmd.execute();
executedCommands.push(cmd);
} catch (Exception e) {
compensate();
}
}
private void compensate() {
while (!executedCommands.isEmpty()) {
executedCommands.pop().compensate();
}
}
}
4.4 基于Redis List的命令队列
Redis的List数据结构天然支持命令队列的生产消费模式。
// 生产者
public class RedisCommandProducer {
@Autowired
private StringRedisTemplate redisTemplate;
public void produce(Command cmd) {
String json = JSON.toJSONString(cmd);
redisTemplate.opsForList().leftPush("command:queue", json);
}
}
// 消费者
public class RedisCommandConsumer {
@Autowired
private StringRedisTemplate redisTemplate;
private volatile boolean running = true;
@PostConstruct
public void start() {
new Thread(() -> {
while (running) {
// BRPOP阻塞获取,超时时间5秒
String json = redisTemplate.opsForList()
.rightPop("command:queue", 5, TimeUnit.SECONDS);
if (json != null) {
Command cmd = JSON.parseObject(json, Command.class);
cmd.execute();
}
}
}).start();
}
}
4.5 分布式命令执行流程图
flowchart LR
A[Client] -->|序列化Command| B[Producer]
B -->|发送消息| C{Message Broker}
C -->|推送/拉取| D[Consumer]
D -->|反序列化| E[Command.execute]
E -->|调用| F[Receiver]
F -->|执行结果| G[(Database)]
E -->|记录日志| H[(Command Log)]
subgraph 失败处理
E -->|异常| I[Compensation]
I -->|补偿操作| F
end
流程图说明:该流程图展示了分布式环境下命令从产生到执行的完整链路。客户端创建命令对象后,生产者将其序列化为JSON/Protobuf等格式发送至消息中间件(如RabbitMQ、Kafka)。消息中间件作为命令缓冲与分发中心,负责将命令可靠地投递给消费者。消费者接收到消息后反序列化还原为命令对象,并调用其execute()方法。命令内部委托给接收者执行实际业务逻辑(如数据库操作)。若执行过程中发生异常,则触发补偿流程:命令的compensate()方法被调用,执行反向操作以保证数据最终一致性。同时,所有命令执行前后均可记录日志,用于审计与故障恢复。这种架构将命令的生产与执行完全解耦,极大提升了系统的伸缩性与容错能力。
五、对比辨析
5.1 命令模式 vs 策略模式
| 维度 | 命令模式 | 策略模式 |
|---|---|---|
| 核心意图 | 将请求封装为对象,解耦调用者与执行者,支持撤销 | 封装一系列算法,使它们可以互相替换 |
| 关注点 | 行为参数化,强调“做什么”及何时做 | 算法族管理,强调“怎么做” |
| 撤销能力 | 内置undo()方法,天然支持撤销 | 通常不支持撤销 |
| 调用者角色 | 必须有Invoker角色持有命令并触发执行 | 无Invoker概念,由Context直接调用策略 |
| 典型应用 | GUI按钮、事务、宏命令、任务队列 | 支付方式选择、排序算法、压缩算法 |
5.2 命令模式 vs 责任链模式
命令模式中请求发送者与接收者是一对一关系(通过命令对象精确指定);而责任链模式中请求沿链传递,可能被多个处理器处理或无人处理。两者可结合使用:责任链中的每个处理器可封装为命令。
5.3 命令模式 vs 备忘录模式
命令模式通常配合备忘录模式实现撤销:执行命令前,命令对象创建接收者的备忘录快照;撤销时,从备忘录恢复状态。命令模式关注行为的封装,备忘录模式关注状态的保存与恢复。
// 命令模式配合备忘录实现撤销
class InsertCommand implements Command {
private Document doc;
private Memento backup; // 备忘录
@Override
public void execute() {
backup = doc.createMemento(); // 保存快照
doc.insert(text);
}
@Override
public void undo() {
doc.restoreFromMemento(backup);
}
}
5.4 命令模式 vs 观察者模式
- 命令模式:主动调用,调用者明确知道要执行哪个命令。
- 观察者模式:被动通知,主题状态变化后广播给所有观察者,观察者无法预期通知时机。
5.5 命令模式与函数式接口
Java 8的Lambda表达式可大幅简化命令模式的实现,尤其当命令仅有一个方法时。
// 传统方式
remote.setCommand(0, new LightOnCommand(light), new LightOffCommand(light));
// Lambda简化
remote.setCommand(0,
() -> light.turnOn(), // execute
() -> light.turnOff() // undo
);
局限性:Lambda无法携带状态(如撤销所需的快照),复杂场景仍需具体命令类。
六、适用场景分析(含完整Demo与Mermaid图)
场景一:GUI按钮与菜单操作
Demo:文本编辑器的复制、粘贴、剪切命令
// 接收者:文本编辑器
class TextEditor {
private String text = "";
private String clipboard = "";
public void setText(String text) { this.text = text; }
public String getText() { return text; }
public void copy() { clipboard = text; }
public void cut() { clipboard = text; text = ""; }
public void paste() { text += clipboard; }
}
// 命令接口
interface EditorCommand {
void execute();
void undo();
}
// 具体命令
class CopyCommand implements EditorCommand {
private TextEditor editor;
public CopyCommand(TextEditor editor) { this.editor = editor; }
public void execute() { editor.copy(); }
public void undo() { /* 不可撤销 */ }
}
class CutCommand implements EditorCommand {
private TextEditor editor;
private String backup;
public CutCommand(TextEditor editor) { this.editor = editor; }
public void execute() { backup = editor.getText(); editor.cut(); }
public void undo() { editor.setText(backup); }
}
class PasteCommand implements EditorCommand {
private TextEditor editor;
private String backup;
public PasteCommand(TextEditor editor) { this.editor = editor; }
public void execute() { backup = editor.getText(); editor.paste(); }
public void undo() { editor.setText(backup); }
}
// 调用者:编辑器工具栏
class EditorToolbar {
private Stack<EditorCommand> undoStack = new Stack<>();
private Map<String, EditorCommand> commands = new HashMap<>();
public void registerCommand(String name, EditorCommand cmd) {
commands.put(name, cmd);
}
public void click(String name) {
EditorCommand cmd = commands.get(name);
if (cmd != null) {
cmd.execute();
if (!(cmd instanceof CopyCommand)) {
undoStack.push(cmd);
}
}
}
public void undo() {
if (!undoStack.isEmpty()) {
undoStack.pop().undo();
}
}
}
// 客户端
public class EditorDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
EditorToolbar toolbar = new EditorToolbar();
toolbar.registerCommand("copy", new CopyCommand(editor));
toolbar.registerCommand("cut", new CutCommand(editor));
toolbar.registerCommand("paste", new PasteCommand(editor));
editor.setText("Hello ");
toolbar.click("copy");
toolbar.click("paste"); // Hello Hello
toolbar.undo(); // Hello
}
}
Mermaid交互流程图
flowchart TD
A[用户点击按钮] --> B{Toolbar.click}
B --> C[获取对应Command]
C --> D[Command.execute]
D --> E{命令类型}
E -->|Copy| F[Receiver.copy]
E -->|Cut| G[备份状态 + Receiver.cut]
E -->|Paste| H[备份状态 + Receiver.paste]
G --> I[压入undoStack]
H --> I
I --> J[更新UI]
F --> J
J --> K[结束]
L[用户点击撤销] --> M[弹出undoStack顶部命令]
M --> N[Command.undo]
N --> O[恢复备份状态]
O --> J
文字说明:该流程图揭示了GUI应用中命令模式的核心协作机制。当用户点击工具栏按钮时,EditorToolbar作为Invoker根据按钮标识取出预先注册的EditorCommand对象,调用其execute()方法。具体命令对象内部委托给TextEditor接收者执行相应的文本操作。对于CutCommand和PasteCommand等可撤销命令,在执行前会备份当前文本状态,并将自身压入undoStack。当用户触发撤销操作时,调用者从栈中弹出最近执行的命令并调用undo(),命令对象再利用先前备份的状态恢复编辑器内容。这种设计将UI事件与业务逻辑彻底解耦,使得新增菜单项或快捷键变得极为简单——只需创建新的命令类并注册即可,完全无需修改现有代码。撤销栈的设计更是为用户提供了流畅的操作体验。
场景二:在线点餐系统
Demo:服务员与厨师命令模式
// 接收者:厨师
class Chef {
public void makeDish(String dishName) {
System.out.println("厨师正在制作:" + dishName);
}
public void cancelDish(String dishName) {
System.out.println("厨师取消制作:" + dishName);
}
}
// 命令接口
interface OrderCommand {
void execute();
void undo();
}
// 具体命令:点菜命令
class OrderDishCommand implements OrderCommand {
private Chef chef;
private String dishName;
public OrderDishCommand(Chef chef, String dishName) {
this.chef = chef;
this.dishName = dishName;
}
@Override
public void execute() { chef.makeDish(dishName); }
@Override
public void undo() { chef.cancelDish(dishName); }
}
// 宏命令:套餐命令
class SetMealCommand implements OrderCommand {
private List<OrderCommand> commands = new ArrayList<>();
public void addCommand(OrderCommand cmd) { commands.add(cmd); }
@Override
public void execute() {
commands.forEach(OrderCommand::execute);
}
@Override
public void undo() {
for (int i = commands.size() - 1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
// 调用者:服务员
class Waiter {
private Queue<OrderCommand> orderQueue = new LinkedList<>();
private Stack<OrderCommand> history = new Stack<>();
public void takeOrder(OrderCommand cmd) {
orderQueue.offer(cmd);
System.out.println("服务员接收订单:" + cmd.getClass().getSimpleName());
}
public void sendToKitchen() {
while (!orderQueue.isEmpty()) {
OrderCommand cmd = orderQueue.poll();
cmd.execute();
history.push(cmd);
}
}
public void cancelLast() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
// 客户端
public class RestaurantDemo {
public static void main(String[] args) {
Chef chef = new Chef();
Waiter waiter = new Waiter();
// 单点
waiter.takeOrder(new OrderDishCommand(chef, "宫保鸡丁"));
// 套餐(宏命令)
SetMealCommand setMeal = new SetMealCommand();
setMeal.addCommand(new OrderDishCommand(chef, "米饭"));
setMeal.addCommand(new OrderDishCommand(chef, "鱼香肉丝"));
setMeal.addCommand(new OrderDishCommand(chef, "紫菜蛋花汤"));
waiter.takeOrder(setMeal);
waiter.sendToKitchen(); // 批量发送至厨房
waiter.cancelLast(); // 撤销套餐
}
}
Mermaid时序图
sequenceDiagram
participant Customer as 顾客
participant Waiter as 服务员(Invoker)
participant OrderCmd as 订单命令
participant Chef as 厨师(Receiver)
Customer->>Waiter: 点菜("宫保鸡丁")
Waiter->>OrderCmd: new OrderDishCommand(chef, "宫保鸡丁")
Waiter->>Waiter: orderQueue.offer(cmd)
Customer->>Waiter: 点套餐
Waiter->>OrderCmd: new SetMealCommand(...)
Waiter->>Waiter: orderQueue.offer(setMeal)
Customer->>Waiter: 下单完成,请上菜
Waiter->>Waiter: sendToKitchen()
loop 处理订单队列
Waiter->>OrderCmd: cmd.execute()
OrderCmd->>Chef: makeDish(dishName)
Chef-->>OrderCmd: 制作完成
OrderCmd-->>Waiter: 返回
end
Customer->>Waiter: 取消最后一道菜
Waiter->>OrderCmd: history.pop().undo()
OrderCmd->>Chef: cancelDish(dishName)
Chef-->>OrderCmd: 取消完成
OrderCmd-->>Waiter: 返回
文字说明:该时序图生动描绘了餐厅点餐系统中命令模式的协作流程。顾客(Client)向服务员(Invoker)下达点菜请求,服务员并不直接与厨师交互,而是将请求封装为OrderDishCommand或SetMealCommand对象并暂存于队列中。这种设计允许服务员在高峰时段先收集订单,待后厨空闲时再批量发送,起到了天然的流量削峰作用。当调用sendToKitchen()时,服务员遍历队列依次执行命令,命令对象内部将操作委托给厨师(Receiver)的makeDish()方法。若顾客中途取消,服务员只需从历史栈中取出最后执行的命令并调用undo(),厨师便会收到取消通知。宏命令SetMealCommand更是将多个菜品命令组合为一个原子单元,执行时批量处理,撤销时逆序补偿,完美支持了套餐场景。这种解耦使得服务员与厨师可以独立演化,新增菜品也无需修改服务员代码。
场景三:文本编辑器撤销/重做
Demo:基于双栈的撤销重做实现
// 备忘录:状态快照
class TextMemento {
private String content;
public TextMemento(String content) { this.content = content; }
public String getContent() { return content; }
}
// 接收者:可快照的文档
class SnapshotableDocument {
private StringBuilder content = new StringBuilder();
public void insert(int pos, String text) { content.insert(pos, text); }
public void delete(int pos, int len) { content.delete(pos, pos + len); }
public String getContent() { return content.toString(); }
public TextMemento createMemento() { return new TextMemento(content.toString()); }
public void restoreMemento(TextMemento memento) {
content = new StringBuilder(memento.getContent());
}
}
// 命令接口
interface UndoableCommand {
void execute();
void undo();
void redo(); // 重做即再次执行
}
// 插入命令
class InsertCommand implements UndoableCommand {
private SnapshotableDocument doc;
private int position;
private String text;
private TextMemento backup;
public InsertCommand(SnapshotableDocument doc, int pos, String text) {
this.doc = doc;
this.position = pos;
this.text = text;
}
@Override
public void execute() {
backup = doc.createMemento(); // 保存快照
doc.insert(position, text);
}
@Override
public void undo() {
doc.restoreMemento(backup);
}
@Override
public void redo() {
execute(); // 重做即再次插入(需重新计算position)
}
}
// 调用者:命令管理器
class CommandManager {
private Stack<UndoableCommand> undoStack = new Stack<>();
private Stack<UndoableCommand> redoStack = new Stack<>();
public void executeCommand(UndoableCommand cmd) {
cmd.execute();
undoStack.push(cmd);
redoStack.clear(); // 新命令清空重做栈
}
public void undo() {
if (!undoStack.isEmpty()) {
UndoableCommand cmd = undoStack.pop();
cmd.undo();
redoStack.push(cmd);
}
}
public void redo() {
if (!redoStack.isEmpty()) {
UndoableCommand cmd = redoStack.pop();
cmd.redo();
undoStack.push(cmd);
}
}
}
// 客户端
public class UndoRedoDemo {
public static void main(String[] args) {
SnapshotableDocument doc = new SnapshotableDocument();
CommandManager manager = new CommandManager();
manager.executeCommand(new InsertCommand(doc, 0, "Hello"));
manager.executeCommand(new InsertCommand(doc, 5, " World"));
System.out.println(doc.getContent()); // Hello World
manager.undo(); // 撤销 " World"
System.out.println(doc.getContent()); // Hello
manager.redo(); // 重做 " World"
System.out.println(doc.getContent()); // Hello World
}
}
Mermaid双栈操作流程图
flowchart TD
subgraph 执行命令
A[新命令] --> B[cmd.execute]
B --> C[push undoStack]
C --> D[clear redoStack]
end
subgraph 撤销操作
E[用户点击Undo] --> F{undoStack是否为空}
F -->|非空| G[pop undoStack -> cmd]
G --> H[cmd.undo]
H --> I[push redoStack]
end
subgraph 重做操作
J[用户点击Redo] --> K{redoStack是否为空}
K -->|非空| L[pop redoStack -> cmd]
L --> M[cmd.redo]
M --> N[push undoStack]
end
文字说明:文本编辑器的撤销/重做是命令模式的经典应用,其核心在于双栈结构的精妙设计。当用户执行一个新命令时,命令管理器调用cmd.execute()完成操作,并将该命令压入undoStack(撤销栈),同时清空redoStack(重做栈)。这是因为新命令执行后,之前保存的重做历史已失效。当用户点击撤销时,管理器从undoStack弹出最近命令,调用其undo()方法恢复状态,再将该命令转移至redoStack以备重做。重做操作则反向行之:从redoStack弹出命令,调用redo()(通常就是再次执行execute()),并将其推回undoStack。这种双栈协同机制实现了无限级数的撤销与重做,且内存占用仅与历史步数线性相关。配合备忘录模式,命令对象在执行前保存文档快照,撤销时直接恢复快照,避免了复杂的逆操作计算,尤其适用于插入、删除、格式化等复杂操作。
场景四:定时任务调度系统
Demo:基于ScheduledThreadPoolExecutor的命令调度器
import java.util.concurrent.*;
// 命令接口
@FunctionalInterface
interface ScheduledCommand {
void execute();
}
// 具体命令:备份任务
class BackupCommand implements ScheduledCommand {
@Override
public void execute() {
System.out.println("执行数据库备份..." + System.currentTimeMillis());
}
}
// 调用者:任务调度器
class CommandScheduler {
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
private Map<String, ScheduledFuture<?>> futures = new ConcurrentHashMap<>();
// 延迟执行
public void scheduleOnce(String taskId, ScheduledCommand cmd, long delay, TimeUnit unit) {
ScheduledFuture<?> future = scheduler.schedule(cmd::execute, delay, unit);
futures.put(taskId, future);
}
// 周期执行
public void schedulePeriodic(String taskId, ScheduledCommand cmd,
long initialDelay, long period, TimeUnit unit) {
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(
cmd::execute, initialDelay, period, unit);
futures.put(taskId, future);
}
public void cancel(String taskId) {
ScheduledFuture<?> future = futures.remove(taskId);
if (future != null) future.cancel(false);
}
public void shutdown() { scheduler.shutdown(); }
}
// 客户端
public class SchedulerDemo {
public static void main(String[] args) throws InterruptedException {
CommandScheduler scheduler = new CommandScheduler();
// 延迟5秒执行一次
scheduler.scheduleOnce("backup", new BackupCommand(), 5, TimeUnit.SECONDS);
// 每秒执行一次健康检查
scheduler.schedulePeriodic("healthCheck",
() -> System.out.println("健康检查通过"),
0, 1, TimeUnit.SECONDS);
Thread.sleep(5000);
scheduler.cancel("healthCheck");
scheduler.shutdown();
}
}
Mermaid任务调度流程图
flowchart TD
A[客户端提交Command] --> B[CommandScheduler]
B --> C{调度类型}
C -->|延迟执行| D[scheduleOnce]
C -->|周期执行| E[scheduleAtFixedRate]
D --> F[包装为Runnable]
E --> F
F --> G[提交至ScheduledThreadPoolExecutor]
G --> H[延迟队列DelayQueue]
H --> I[时间到达]
I --> J[Worker线程取出任务]
J --> K[执行Command.execute]
K --> L{是否周期任务}
L -->|是| M[重新计算下次执行时间]
M --> H
L -->|否| N[任务结束]
文字说明:本流程图展示了命令模式在定时任务调度中的运行机制。CommandScheduler作为Invoker接收客户端提交的ScheduledCommand对象,并根据调度类型(一次性或周期性)选择合适的调度方法。在底层,调度器将命令对象的execute()方法适配为Runnable,并提交至ScheduledThreadPoolExecutor。该线程池内部维护一个基于堆的DelayedWorkQueue(延迟队列),所有任务按触发时间排序。当时间到达时,工作线程从队列头部取出任务并执行命令的execute()方法。对于周期任务,执行完毕后会重新计算下次触发时间并再次入队,形成循环。相比直接向线程池提交Runnable,命令模式提供了更高层次的抽象:命令对象可以携带元数据(任务ID、优先级、参数)、支持序列化持久化、易于监控与治理。此外,通过CommandScheduler统一管理Future句柄,客户端可以方便地取消任务或查询状态,极大提升了调度的灵活性与可控性。
场景五:数据库事务命令封装
Demo:模拟事务管理器与命令回滚
import java.util.ArrayList;
import java.util.List;
// 模拟数据库连接
class MockConnection {
public void executeSQL(String sql) {
System.out.println("执行SQL: " + sql);
}
}
// 命令接口
interface TransactionalCommand {
void execute(MockConnection conn);
void undo(MockConnection conn); // 补偿SQL
}
// 具体命令:插入用户
class InsertUserCommand implements TransactionalCommand {
private int userId;
private String userName;
public InsertUserCommand(int id, String name) {
this.userId = id;
this.userName = name;
}
@Override
public void execute(MockConnection conn) {
String sql = String.format("INSERT INTO users VALUES (%d, '%s')", userId, userName);
conn.executeSQL(sql);
}
@Override
public void undo(MockConnection conn) {
String sql = String.format("DELETE FROM users WHERE id = %d", userId);
conn.executeSQL(sql);
}
}
// 调用者:事务管理器
class TransactionManager {
private List<TransactionalCommand> commands = new ArrayList<>();
private MockConnection connection = new MockConnection();
public void addCommand(TransactionalCommand cmd) {
commands.add(cmd);
}
public void commit() {
List<TransactionalCommand> executed = new ArrayList<>();
try {
System.out.println("--- 事务开始 ---");
for (TransactionalCommand cmd : commands) {
cmd.execute(connection);
executed.add(cmd);
}
System.out.println("--- 事务提交 ---");
} catch (Exception e) {
System.err.println("事务执行异常,开始回滚...");
// 逆序回滚
for (int i = executed.size() - 1; i >= 0; i--) {
executed.get(i).undo(connection);
}
System.out.println("--- 事务回滚完成 ---");
} finally {
commands.clear();
}
}
}
// 客户端
public class TransactionDemo {
public static void main(String[] args) {
TransactionManager tm = new TransactionManager();
tm.addCommand(new InsertUserCommand(1, "Alice"));
tm.addCommand(new InsertUserCommand(2, "Bob"));
tm.commit(); // 成功执行
// 模拟部分失败(此处演示手动抛异常场景)
tm.addCommand(new InsertUserCommand(3, "Charlie"));
tm.addCommand(() -> { throw new RuntimeException("模拟失败"); });
tm.commit(); // 触发回滚
}
}
Mermaid事务流程图
flowchart TD
A[TransactionManager.commit] --> B[遍历Command列表]
B --> C[cmd.execute]
C --> D{是否异常}
D -->|成功| E[记录至executed列表]
E --> F{还有命令?}
F -->|是| B
F -->|否| G[提交成功]
D -->|异常| H[触发回滚流程]
H --> I[逆序遍历executed列表]
I --> J[cmd.undo]
J --> K{还有命令?}
K -->|是| I
K -->|否| L[回滚完成]
文字说明:该流程图揭示了命令模式在数据库事务管理中的典型应用。TransactionManager作为Invoker维护一个命令列表,当客户端调用commit()时,它遍历所有TransactionalCommand对象并依次调用execute()方法。每个命令内部将具体的SQL操作委托给MockConnection执行。如果在执行过程中任何命令抛出异常,事务管理器立即触发回滚流程:逆序遍历已成功执行的命令列表(executed),并调用每个命令的undo()方法执行补偿SQL。这种设计使得事务中的每个操作都被封装为独立的命令对象,天然支持审计日志(可在执行前后记录SQL与参数)、重试机制(异常时重试若干次)、以及分布式场景下的最终一致性(将命令序列化后发送至消息队列)。与传统的编程式事务相比,命令模式提供了更细粒度的控制力与更清晰的职责分离。
七、面试题精选与专家级解答
1. 命令模式和策略模式在结构上很相似,它们的本质区别是什么?
解答:两者均将行为封装为对象,但意图截然不同。策略模式关注算法的可替换性,不同策略实现同一目标的不同计算方法,调用者(Context)通常直接调用策略方法,无需Invoker角色;命令模式关注请求的封装与参数化,强调将操作请求者与执行者解耦,支持撤销、队列、日志等增强功能。一个典型的判断标准是:是否需要Invoker持有命令并延迟执行?是否需要撤销?若是,则倾向于命令模式。
2. Runnable接口的设计是如何体现命令模式的?Thread作为Invoker的角色是如何工作的?
解答:Runnable是JDK中最经典的命令接口,其run()方法即execute()。Thread作为Invoker,在构造函数中接收Runnable命令对象,并在start()方法触发的线程上下文中调用target.run()。与标准命令模式略有差异的是,Thread自身也实现了Runnable,使得一个线程可以作为另一个线程的命令,实现了命令的嵌套。
3. 如何用命令模式实现一个支持撤销和重做的文本编辑器?请简述数据结构设计。
解答:核心数据结构为双栈:undoStack与redoStack。每次执行命令时,将其压入undoStack并清空redoStack。撤销时,从undoStack弹出命令,调用undo()后压入redoStack。重做时,从redoStack弹出命令,调用redo()后压回undoStack。命令对象需在执行前保存接收者状态快照(配合备忘录模式)或提供反向操作逻辑。
4. 在分布式系统中,如何利用命令模式实现最终一致性的Saga事务?
解答:Saga将长事务拆分为多个本地事务,每个本地事务封装为一个SagaCommand,包含execute()和compensate()方法。Saga协调器作为Invoker按序执行命令,若某步骤失败,则逆序调用已执行命令的compensate()进行补偿。命令对象可序列化后通过消息队列传递,保证执行的可靠性与可追溯性。
5. 命令模式中的宏命令(MacroCommand)与组合模式有何关系?
解答:宏命令是组合模式在命令模式中的典型应用。MacroCommand实现Command接口并持有一组子命令,其execute()遍历子命令执行,undo()逆序遍历子命令撤销。这完全符合组合模式中“将对象组合成树形结构以表示部分-整体层次”的思想,使得客户端可以统一对待单个命令与复合命令。
6. Spring中的CommandLineRunner和ApplicationRunner的应用场景有哪些?它们与命令模式的异同?
解答:两者均用于Spring Boot启动后执行一次性初始化任务,如数据预热、缓存加载。它们是命令模式的简化应用:CommandLineRunner即Command接口,SpringApplication作为Invoker在容器刷新后遍历所有Runner执行。与传统命令模式的区别在于缺少显式的Receiver(业务逻辑直接在Runner中实现)和撤销机制,但解耦意图一致。
7. 如何利用命令模式+消息队列实现系统的异步操作与流量削峰?
解答:将业务操作封装为Command对象并序列化(如JSON)发送至消息队列。消费者从队列中拉取消息,反序列化为命令对象后调用execute()执行。消息队列天然提供缓冲能力,消费者可根据自身处理能力限流消费,实现削峰。命令对象的引入使得生产者和消费者完全解耦,且便于增加审计、重试、死信处理等横切关注点。
8. 命令模式的撤销功能要求命令对象保存哪些状态?与备忘录模式如何配合?
解答:命令对象需保存足以恢复接收者状态的信息,通常有两种方式:一是保存反向操作所需参数(如插入命令保存删除位置与长度);二是保存接收者的完整状态快照(备忘录)。后者更通用,尤其适用于复杂状态变更。配合时,命令在执行前调用receiver.createMemento()获取快照并持有,undo()时调用receiver.restoreMemento(memento)恢复。
9. 在微服务架构中,命令模式与CQRS(命令查询职责分离)有何关联?
解答:CQRS将系统操作分为命令(写)与查询(读)两类模型。命令模式天然适合封装“写”操作:每个命令对象代表一个意图明确的状态变更请求,可被路由至对应的聚合根执行,并产生领域事件。命令对象还可作为数据传输载体在微服务间传递。CQRS中的Command Bus本质上是命令模式的Invoker扩展,负责将命令分发至对应的处理器。
10. 如何使用Java 8的Lambda表达式简化命令模式的实现?这种简化有什么局限性?
解答:当命令接口为函数式接口(仅含一个抽象方法)时,可直接用Lambda表达式替代具体命令类,例如remote.setCommand(0, light::turnOn, light::turnOff)。局限性在于:Lambda无法持有状态(如撤销所需的备份数据),无法实现复杂的undo逻辑;代码可读性在复杂命令中下降;难以通过反射或注解进行增强。因此,Lambda适用于简单、无状态、无需撤销的命令场景。
结语
命令模式以其“将行为物化”的哲学,在软件工程的各个层面都留下了深刻的印记。从GUI按钮的回调到分布式事务的编排,从JDK的Runnable到Dubbo的Invoker,这一模式始终贯穿于我们对“请求”的抽象与解耦之中。掌握命令模式,不仅是掌握一种设计技巧,更是获得一种将过程转化为对象、将调用转化为数据的思想武器。希望本文能助你在架构设计与源码阅读中,以命令模式的视角洞察更清晰的逻辑脉络。