设计模式详解-命令模式

设计模式详解:命令模式

一、模式概述

命令模式(Command Pattern)是行为型设计模式中最具架构解耦价值的模式之一,其核心意图在于将请求封装为对象,从而支持请求的参数化、队列化、日志化和可撤销操作。命令模式将"调用操作的对象"与"知道如何执行该操作的对象"彻底分离,使请求的发送者和接收者之间不存在直接引用关系。

命令模式的命名直接揭示了其功能——"命令"即一个携带完整执行信息的指令对象。在餐厅场景中,服务员将顾客的点餐请求封装为订单(命令对象),厨师无需知道顾客是谁,只需按订单执行;在操作系统中,用户操作被封装为系统命令,支持撤销重做和宏录制;在分布式系统中,操作请求被序列化为命令消息,通过网络投递到远程节点执行。这些现实隐喻均指向同一核心特征:将行为请求转化为可存储、可传递、可编排的数据结构

命令模式的深层价值在于将行为从调用时绑定转变为运行时组装。传统方法调用是编译期确定的硬编码关系,而命令模式使行为成为一等公民,可以被延迟执行、批量调度、组合编排、持久化恢复。在事务管理、任务调度、操作审计、多级撤销、远程调用等场景中,命令模式是实现系统灵活性和可靠性的关键基础设施。

二、模式结构

命令模式包含四个核心角色,形成清晰的请求-执行解耦架构:

命令接口(Command):声明执行操作的统一接口,通常为execute()方法,可选undo()方法。

具体命令(Concrete Command):实现命令接口,绑定接收者对象,调用接收者的相应方法完成请求。命令对象封装了完整的执行上下文。

接收者(Receiver):知道如何实施与执行一个请求相关的操作,是业务逻辑的真正执行者。

调用者(Invoker):持有命令对象,在适当的时候触发命令执行。调用者不需要知道命令的具体实现,只依赖抽象接口。

客户端(Client):创建具体命令对象并设定其接收者,将命令装配到调用者中。

命令模式的关键特征在于命令对象成为调用者和接收者之间的中介。调用者只发号施令,接收者只管执行,两者通过命令对象松耦合。这种分离使命令可以被存储、传递、组合、延迟执行,突破了方法调用的时空限制。

三、深度案例:分布式电商订单履约平台

以下展示一个真实场景下的命令模式应用——电商平台的订单履约系统,处理订单创建、库存扣减、支付确认、物流发货等跨服务事务,支持异步执行、失败重试、操作审计和事务回滚。

3.1 问题域分析:直接调用的耦合困境

java
// 反模式:服务间的直接过程调用 @Service public class NaiveOrderFulfillmentService { @Autowired private InventoryService inventory; @Autowired private PaymentService payment; @Autowired private LogisticsService logistics; @Autowired private NotificationService notification; @Transactional public void fulfillOrder(String orderId) { // 直接调用库存扣减 inventory.deduct(orderId); // 直接调用支付确认 payment.capture(orderId); // 直接调用物流创建 logistics.createShipment(orderId); // 直接调用通知发送 notification.sendConfirmation(orderId); // 问题: // 1. 同步阻塞,整体耗时等于各服务耗时之和 // 2. 任一服务失败,事务回滚,已执行的操作需手动补偿 // 3. 无法支持异步执行、批量处理、优先级调度 // 4. 操作无法持久化,系统崩溃后状态丢失 // 5. 无法审计追踪,不知道哪个步骤何时执行 // 6. 无法灵活编排,新增步骤需修改核心代码 } }

上述代码中,调用链与执行链深度耦合,同步阻塞性能差,失败处理复杂,缺乏可观测性和可扩展性。命令模式通过将每个操作封装为独立命令,实现异步执行、持久化、调度编排和审计追踪。

3.2 抽象层:命令接口与执行上下文

java
/** * 命令接口:所有履约命令的统一契约 */ public interface FulfillmentCommand { /** * 全局唯一命令标识 */ String getCommandId(); /** * 关联的订单标识 */ String getOrderId(); /** * 命令类型 */ CommandType getType(); /** * 执行优先级(数值越小优先级越高) */ int getPriority(); /** * 执行命令 * @param context 执行上下文 * @return 执行结果 */ CommandResult execute(ExecutionContext context); /** * 补偿命令(支持事务回滚) * @param context 执行上下文 * @return 补偿结果 */ CommandResult compensate(ExecutionContext context); /** * 序列化为持久化格式 */ String serialize(); /** * 反序列化构造 */ static FulfillmentCommand deserialize(String data); } /** * 命令类型枚举 */ public enum CommandType { INVENTORY_DEDUCT("库存扣减", true), // 需要补偿 INVENTORY_RELEASE("库存释放", false), PAYMENT_CAPTURE("支付确认", true), PAYMENT_REFUND("支付退款", false), SHIPMENT_CREATE("物流创建", true), SHIPMENT_CANCEL("物流取消", false), NOTIFICATION_SEND("通知发送", false), // 无需补偿 POINTS_GRANT("积分发放", true), POINTS_REVOKE("积分回收", false); private final String displayName; private final boolean compensable; // 是否支持补偿 CommandType(String displayName, boolean compensable) { this.displayName = displayName; this.compensable = compensable; } } /** * 执行上下文:贯穿命令执行的环境信息 */ public class ExecutionContext { private final String traceId; private final LocalDateTime startTime; private final Map<String, Object> metadata; private final CommandLogRepository logRepository; private final MeterRegistry metrics; public ExecutionContext(String traceId, CommandLogRepository logRepository, MeterRegistry metrics) { this.traceId = traceId; this.startTime = LocalDateTime.now(); this.metadata = new ConcurrentHashMap<>(); this.logRepository = logRepository; this.metrics = metrics; } public void setAttribute(String key, Object value) { metadata.put(key, value); } @SuppressWarnings("unchecked") public <T> T getAttribute(String key) { return (T) metadata.get(key); } public void logStep(String commandId, String step, Object detail) { CommandLog log = CommandLog.builder() .traceId(traceId) .commandId(commandId) .step(step) .detail(JsonUtils.toJson(detail)) .timestamp(LocalDateTime.now()) .build(); logRepository.save(log); } public void recordMetric(String commandType, String status, long durationMs) { metrics.timer("command.execution", "type", commandType, "status", status) .record(durationMs, TimeUnit.MILLISECONDS); } } /** * 命令执行结果 */ public class CommandResult { public enum Status { SUCCESS, FAILURE, TIMEOUT, RETRYABLE } private final Status status; private final String message; private final Object payload; private final Throwable error; private final boolean retryable; private CommandResult(Status status, String message, Object payload, Throwable error, boolean retryable) { this.status = status; this.message = message; this.payload = payload; this.error = error; this.retryable = retryable; } public static CommandResult success(Object payload) { return new CommandResult(Status.SUCCESS, "OK", payload, null, false); } public static CommandResult failure(String message, Throwable error) { return new CommandResult(Status.FAILURE, message, null, error, false); } public static CommandResult retryable(String message, Throwable error) { return new CommandResult(Status.RETRYABLE, message, null, error, true); } public boolean isSuccess() { return status == Status.SUCCESS; } public boolean isRetryable() { return retryable; } // getters... }

3.3 具体命令:库存扣减与释放

java
/** * 库存扣减命令 */ @Component public class InventoryDeductCommand implements FulfillmentCommand { private final String commandId; private final String orderId; private final List<InventoryItem> items; private final Instant createTime; @Autowired private InventoryServiceClient inventoryClient; @Autowired private InventoryDeductRepository repository; public InventoryDeductCommand(String orderId, List<InventoryItem> items) { this.commandId = "INV_DED_" + UUID.randomUUID().toString().replace("-", ""); this.orderId = orderId; this.items = new ArrayList<>(items); this.createTime = Instant.now(); } @Override public String getCommandId() { return commandId; } @Override public String getOrderId() { return orderId; } @Override public CommandType getType() { return CommandType.INVENTORY_DEDUCT; } @Override public int getPriority() { return 100; } // 高优先级 @Override public CommandResult execute(ExecutionContext context) { long start = System.currentTimeMillis(); context.logStep(commandId, "EXECUTE_START", Map.of("items", items)); try { // 幂等性检查 if (repository.exists(commandId)) { context.logStep(commandId, "EXECUTE_IDEMPOTENT", "Already processed"); return CommandResult.success(repository.findById(commandId)); } // 执行扣减 DeductRequest request = DeductRequest.builder() .commandId(commandId) .orderId(orderId) .items(items.stream() .map(i -> DeductItem.of(i.getSkuId(), i.getQuantity(), i.getWarehouseId())) .collect(Collectors.toList())) .build(); DeductResponse response = inventoryClient.deduct(request); // 持久化结果 InventoryDeductRecord record = InventoryDeductRecord.builder() .commandId(commandId) .orderId(orderId) .items(items) .status(InventoryStatus.DEDUCTED) .deductTime(LocalDateTime.now()) .response(response) .build(); repository.save(record); long duration = System.currentTimeMillis() - start; context.recordMetric("INVENTORY_DEDUCT", "SUCCESS", duration); context.logStep(commandId, "EXECUTE_SUCCESS", response); return CommandResult.success(response); } catch (InventoryInsufficientException e) { long duration = System.currentTimeMillis() - start; context.recordMetric("INVENTORY_DEDUCT", "INSUFFICIENT", duration); context.logStep(commandId, "EXECUTE_INSUFFICIENT", e.getMessage()); return CommandResult.failure("库存不足: " + e.getSkuId(), e); } catch (ServiceUnavailableException e) { long duration = System.currentTimeMillis() - start; context.recordMetric("INVENTORY_DEDUCT", "RETRYABLE", duration); context.logStep(commandId, "EXECUTE_RETRYABLE", e.getMessage()); return CommandResult.retryable("服务暂不可用", e); } catch (Exception e) { long duration = System.currentTimeMillis() - start; context.recordMetric("INVENTORY_DEDUCT", "FAILURE", duration); context.logStep(commandId, "EXECUTE_FAILURE", e.getMessage()); return CommandResult.failure("扣减异常: " + e.getMessage(), e); } } @Override public CommandResult compensate(ExecutionContext context) { context.logStep(commandId, "COMPENSATE_START", null); try { // 查询已扣减记录 InventoryDeductRecord record = repository.findById(commandId); if (record == null || record.getStatus() != InventoryStatus.DEDUCTED) { return CommandResult.success("无需补偿"); } // 执行释放 ReleaseRequest request = ReleaseRequest.builder() .commandId(commandId) .orderId(orderId) .releaseItems(record.getResponse().getAllocatedItems()) .reason("ORDER_CANCELLED") .build(); inventoryClient.release(request); // 更新状态 record.setStatus(InventoryStatus.RELEASED); record.setReleaseTime(LocalDateTime.now()); repository.save(record); context.logStep(commandId, "COMPENSATE_SUCCESS", null); return CommandResult.success("库存已释放"); } catch (Exception e) { context.logStep(commandId, "COMPENSATE_FAILURE", e.getMessage()); // 补偿失败进入人工处理队列 return CommandResult.failure("补偿失败: " + e.getMessage(), e); } } @Override public String serialize() { return JsonUtils.toJson(Map.of( "commandType", getType().name(), "commandId", commandId, "orderId", orderId, "items", items, "createTime", createTime.toString() )); } public static FulfillmentCommand deserialize(String data) { Map<String, Object> map = JsonUtils.fromJson(data); return new InventoryDeductCommand( (String) map.get("orderId"), JsonUtils.convertList(map.get("items"), InventoryItem.class) ); } } /** * 支付确认命令 */ @Component public class PaymentCaptureCommand implements FulfillmentCommand { private final String commandId; private final String orderId; private final BigDecimal amount; private final String paymentMethod; private final String preAuthId; @Autowired private PaymentServiceClient paymentClient; @Autowired private PaymentCaptureRepository repository; public PaymentCaptureCommand(String orderId, BigDecimal amount, String paymentMethod, String preAuthId) { this.commandId = "PAY_CAP_" + UUID.randomUUID().toString().replace("-", ""); this.orderId = orderId; this.amount = amount; this.paymentMethod = paymentMethod; this.preAuthId = preAuthId; } @Override public String getCommandId() { return commandId; } @Override public String getOrderId() { return orderId; } @Override public CommandType getType() { return CommandType.PAYMENT_CAPTURE; } @Override public int getPriority() { return 50; } @Override public CommandResult execute(ExecutionContext context) { context.logStep(commandId, "EXECUTE_START", Map.of("amount", amount, "preAuthId", preAuthId)); try { // 幂等性检查 if (repository.exists(commandId)) { return CommandResult.success(repository.findById(commandId)); } CaptureRequest request = CaptureRequest.builder() .commandId(commandId) .orderId(orderId) .preAuthId(preAuthId) .amount(amount) .idempotencyKey(generateIdempotencyKey()) .build(); CaptureResponse response = paymentClient.capture(request); PaymentCaptureRecord record = PaymentCaptureRecord.builder() .commandId(commandId) .orderId(orderId) .amount(amount) .transactionId(response.getTransactionId()) .status(PaymentStatus.CAPTURED) .captureTime(LocalDateTime.now()) .build(); repository.save(record); context.logStep(commandId, "EXECUTE_SUCCESS", response); return CommandResult.success(response); } catch (PaymentDeclinedException e) { context.logStep(commandId, "EXECUTE_DECLINED", e.getMessage()); return CommandResult.failure("支付被拒: " + e.getReasonCode(), e); } catch (Exception e) { context.logStep(commandId, "EXECUTE_RETRYABLE", e.getMessage()); return CommandResult.retryable("支付服务异常", e); } } @Override public CommandResult compensate(ExecutionContext context) { context.logStep(commandId, "COMPENSATE_START", null); try { PaymentCaptureRecord record = repository.findById(commandId); if (record == null || record.getStatus() != PaymentStatus.CAPTURED) { return CommandResult.success("无需退款"); } RefundRequest request = RefundRequest.builder() .originalTransactionId(record.getTransactionId()) .refundAmount(record.getAmount()) .reason("ORDER_CANCELLED") .build(); RefundResponse response = paymentClient.refund(request); record.setStatus(PaymentStatus.REFUNDED); record.setRefundTime(LocalDateTime.now()); record.setRefundTransactionId(response.getRefundId()); repository.save(record); context.logStep(commandId, "COMPENSATE_SUCCESS", response); return CommandResult.success("已退款"); } catch (Exception e) { context.logStep(commandId, "COMPENSATE_FAILURE", e.getMessage()); return CommandResult.failure("退款失败", e); } } @Override public String serialize() { return JsonUtils.toJson(Map.of( "commandType", getType().name(), "commandId", commandId, "orderId", orderId, "amount", amount, "paymentMethod", paymentMethod, "preAuthId", preAuthId )); } }

3.4 调用者:命令调度与执行引擎

java
/** * 命令调度器:命令模式的调用者核心 */ @Component public class CommandOrchestrator { private final CommandQueue commandQueue; private final CommandExecutor commandExecutor; private final SagaCoordinator sagaCoordinator; private final CommandLogRepository logRepository; private final MeterRegistry metrics; // 命令工厂注册表 private final Map<CommandType, Function<Map<String, Object>, FulfillmentCommand>> commandFactories = new HashMap<>(); @Autowired public CommandOrchestrator(CommandQueue queue, CommandExecutor executor, SagaCoordinator sagaCoordinator, CommandLogRepository logRepository, MeterRegistry metrics) { this.commandQueue = queue; this.commandExecutor = executor; this.sagaCoordinator = sagaCoordinator; this.logRepository = logRepository; this.metrics = metrics; } /** * 注册命令工厂 */ public void registerFactory(CommandType type, Function<Map<String, Object>, FulfillmentCommand> factory) { commandFactories.put(type, factory); } /** * 提交单个命令(异步执行) */ public CompletableFuture<CommandResult> submit(FulfillmentCommand command) { metrics.counter("command.submitted", "type", command.getType().name()).increment(); // 持久化命令 CommandRecord record = CommandRecord.builder() .commandId(command.getCommandId()) .orderId(command.getOrderId()) .type(command.getType()) .status(CommandStatus.PENDING) .priority(command.getPriority()) .payload(command.serialize()) .submitTime(LocalDateTime.now()) .build(); logRepository.save(record); // 入队等待执行 return commandQueue.enqueue(command) .thenApply(v -> commandExecutor.execute(command)); } /** * 编排Saga事务:按顺序执行命令链,支持补偿回滚 */ public CompletableFuture<SagaResult> executeSaga(String orderId, List<FulfillmentCommand> commands) { String sagaId = "SAGA_" + orderId; SagaInstance saga = SagaInstance.builder() .sagaId(sagaId) .orderId(orderId) .status(SagaStatus.RUNNING) .commands(commands.stream() .map(c -> SagaStep.builder() .stepId(c.getCommandId()) .commandType(c.getType()) .status(StepStatus.PENDING) .build()) .collect(Collectors.toList())) .startTime(LocalDateTime.now()) .build(); sagaCoordinator.start(saga); return executeSagaSteps(saga, commands, 0, new ArrayList<>()); } private CompletableFuture<SagaResult> executeSagaSteps(SagaInstance saga, List<FulfillmentCommand> commands, int index, List<CommandResult> results) { if (index >= commands.size()) { sagaCoordinator.complete(saga.getSagaId()); return CompletableFuture.completedFuture( SagaResult.success(saga.getSagaId(), results)); } FulfillmentCommand command = commands.get(index); ExecutionContext context = new ExecutionContext(saga.getSagaId(), logRepository, metrics); return submit(command) .thenCompose(result -> { sagaCoordinator.updateStep(saga.getSagaId(), command.getCommandId(), result.isSuccess() ? StepStatus.SUCCESS : StepStatus.FAILED); if (result.isSuccess()) { results.add(result); return executeSagaSteps(saga, commands, index + 1, results); } else { // 执行补偿 return compensateSaga(saga, commands, index) .thenApply(compensationResults -> SagaResult.failed(saga.getSagaId(), result, compensationResults)); } }); } private CompletableFuture<List<CommandResult>> compensateSaga(SagaInstance saga, List<FulfillmentCommand> commands, int failedIndex) { List<CompletableFuture<CommandResult>> compensations = new ArrayList<>(); // 逆序补偿已执行的命令 for (int i = failedIndex - 1; i >= 0; i--) { FulfillmentCommand command = commands.get(i); if (!command.getType().isCompensable()) continue; ExecutionContext context = new ExecutionContext(saga.getSagaId(), logRepository, metrics); compensations.add(CompletableFuture.supplyAsync(() -> command.compensate(context))); } return CompletableFuture.allOf(compensations.toArray(new CompletableFuture[0])) .thenApply(v -> compensations.stream() .map(CompletableFuture::join) .collect(Collectors.toList())); } /** * 批量重试失败命令 */ public void retryFailedCommands(CommandType type, int batchSize) { List<CommandRecord> failed = logRepository.findRetryable(type, batchSize); failed.parallelStream().forEach(record -> { FulfillmentCommand command = deserialize(record.getPayload()); CommandResult result = commandExecutor.execute(command); if (result.isSuccess()) { record.setStatus(CommandStatus.SUCCESS); } else if (!result.isRetryable()) { record.setStatus(CommandStatus.DEAD_LETTER); } record.setRetryCount(record.getRetryCount() + 1); record.setLastRetryTime(LocalDateTime.now()); logRepository.save(record); }); } private FulfillmentCommand deserialize(String payload) { Map<String, Object> map = JsonUtils.fromJson(payload); CommandType type = CommandType.valueOf((String) map.get("commandType")); Function<Map<String, Object>, FulfillmentCommand> factory = commandFactories.get(type); if (factory == null) throw new IllegalStateException("No factory for " + type); return factory.apply(map); } }

3.5 队列与执行器:异步执行基础设施

java
/** * 优先级阻塞队列:支持命令优先级调度 */ @Component public class PriorityCommandQueue implements CommandQueue { private final PriorityBlockingQueue<QueuedCommand> queue; private final CommandRecordRepository repository; public PriorityCommandQueue(CommandRecordRepository repository) { this.repository = repository; // 按优先级排序,同优先级按提交时间FIFO this.queue = new PriorityBlockingQueue<>(1000, Comparator.comparingInt(QueuedCommand::priority) .thenComparing(QueuedCommand::submitTime)); } @Override public CompletableFuture<Void> enqueue(FulfillmentCommand command) { return CompletableFuture.runAsync(() -> { QueuedCommand qc = new QueuedCommand(command.getCommandId(), command.getPriority(), command.serialize(), Instant.now()); queue.offer(qc); repository.updateStatus(command.getCommandId(), CommandStatus.QUEUED); }); } @Override public FulfillmentCommand dequeue() throws InterruptedException { QueuedCommand qc = queue.take(); repository.updateStatus(qc.commandId(), CommandStatus.PROCESSING); // 反序列化 return deserialize(qc.payload()); } @Override public int size() { return queue.size(); } } /** * 命令执行器:消费队列中的命令 */ @Component public class CommandExecutor { private final CommandQueue queue; private final ExecutorService executor; private final Map<String, FulfillmentCommand> commandRegistry; private volatile boolean running = true; public CommandExecutor(CommandQueue queue, @Value("${command.executor.threads:10}") int threads) { this.queue = queue; this.executor = Executors.newFixedThreadPool(threads); this.commandRegistry = new ConcurrentHashMap<>(); } @PostConstruct public void start() { for (int i = 0; i < ((ThreadPoolExecutor) executor).getCorePoolSize(); i++) { executor.submit(this::consumeLoop); } } private void consumeLoop() { while (running && !Thread.interrupted()) { try { FulfillmentCommand command = queue.dequeue(); execute(command); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } public CommandResult execute(FulfillmentCommand command) { ExecutionContext context = new ExecutionContext( generateTraceId(), null, null); // 简化 long start = System.currentTimeMillis(); CommandResult result = command.execute(context); long duration = System.currentTimeMillis() - start; // 异步持久化结果 persistResult(command, result, duration); return result; } private void persistResult(FulfillmentCommand command, CommandResult result, long duration) { // 实际实现... } @PreDestroy public void shutdown() { running = false; executor.shutdown(); } }

3.6 宏命令:组合命令实现复杂流程

java
/** * 宏命令:将多个命令组合为原子操作单元 */ public class CompositeFulfillmentCommand implements FulfillmentCommand { private final String commandId; private final String orderId; private final List<FulfillmentCommand> commands; private final boolean atomic; // true=全部成功才算成功 public CompositeFulfillmentCommand(String orderId, List<FulfillmentCommand> commands, boolean atomic) { this.commandId = "COMP_" + UUID.randomUUID().toString().replace("-", ""); this.orderId = orderId; this.commands = new ArrayList<>(commands); this.atomic = atomic; } @Override public String getCommandId() { return commandId; } @Override public String getOrderId() { return orderId; } @Override public CommandType getType() { return CommandType.COMPOSITE; } @Override public int getPriority() { return commands.stream().mapToInt(FulfillmentCommand::getPriority).min().orElse(100); } @Override public CommandResult execute(ExecutionContext context) { context.logStep(commandId, "COMPOSITE_START", Map.of("subCommands", commands.size(), "atomic", atomic)); List<CommandResult> results = new ArrayList<>(); for (FulfillmentCommand cmd : commands) { CommandResult result = cmd.execute(context); results.add(result); if (!result.isSuccess()) { context.logStep(commandId, "SUBCOMMAND_FAILED", Map.of("failedCommand", cmd.getCommandId())); if (atomic) { // 原子模式:补偿已执行的命令 compensateExecuted(results, context); return CommandResult.failure("原子命令失败: " + cmd.getCommandId(), null); } // 非原子模式:继续执行其他命令 } } context.logStep(commandId, "COMPOSITE_SUCCESS", results); return CommandResult.success(results); } private void compensateExecuted(List<CommandResult> results, ExecutionContext context) { for (int i = results.size() - 1; i >= 0; i--) { if (results.get(i).isSuccess()) { commands.get(i).compensate(context); } } } @Override public CommandResult compensate(ExecutionContext context) { // 逆序补偿所有子命令 List<CommandResult> results = new ArrayList<>(); for (int i = commands.size() - 1; i >= 0; i--) { results.add(commands.get(i).compensate(context)); } return CommandResult.success(results); } @Override public String serialize() { return JsonUtils.toJson(Map.of( "commandType", getType().name(), "commandId", commandId, "orderId", orderId, "atomic", atomic, "subCommands", commands.stream().map(FulfillmentCommand::serialize).toList() )); } }

3.7 前端操作命令:撤销重做系统

java
/** * 前端文档编辑器的命令模式实现 */ public interface EditorCommand { void execute(); void undo(); String getDescription(); } /** * 插入文本命令 */ public class InsertTextCommand implements EditorCommand { private final DocumentModel document; private final int position; private final String text; private String previousText; public InsertTextCommand(DocumentModel document, int position, String text) { this.document = document; this.position = position; this.text = text; } @Override public void execute() { previousText = document.getContent(); document.insert(position, text); } @Override public void undo() { document.setContent(previousText); } @Override public String getDescription() { return "插入文本: " + text.substring(0, Math.min(text.length(), 20)); } } /** * 格式化命令(粗体/斜体等) */ public class FormatCommand implements EditorCommand { private final DocumentModel document; private final int start, end; private final TextFormat format; private List<TextFormat> previousFormats; public FormatCommand(DocumentModel document, int start, int end, TextFormat format) { this.document = document; this.start = start; this.end = end; this.format = format; } @Override public void execute() { previousFormats = document.getFormats(start, end); document.applyFormat(start, end, format); } @Override public void undo() { document.restoreFormats(start, end, previousFormats); } @Override public String getDescription() { return "应用格式: " + format.name(); } } /** * 命令历史管理器:支持多级撤销重做 */ @Component public class CommandHistory { private final Deque<EditorCommand> undoStack = new ArrayDeque<>(); private final Deque<EditorCommand> redoStack = new ArrayDeque<>(); private final int maxHistory; public CommandHistory(@Value("${editor.history.max:100}") int maxHistory) { this.maxHistory = maxHistory; } public void execute(EditorCommand command) { command.execute(); undoStack.push(command); redoStack.clear(); // 新命令清空重做栈 if (undoStack.size() > maxHistory) { undoStack.removeLast(); // 淘汰最旧的 } } public boolean canUndo() { return !undoStack.isEmpty(); } public void undo() { if (!canUndo()) return; EditorCommand command = undoStack.pop(); command.undo(); redoStack.push(command); } public boolean canRedo() { return !redoStack.isEmpty(); } public void redo() { if (!canRedo()) return; EditorCommand command = redoStack.pop(); command.execute(); undoStack.push(command); } public List<String> getUndoDescriptions() { return undoStack.stream().map(EditorCommand::getDescription).toList(); } public List<String> getRedoDescriptions() { return redoStack.stream().map(EditorCommand::getDescription).toList(); } /** * 批量执行宏命令 */ public void executeMacro(List<EditorCommand> commands) { // 包装为单个可撤销单元 EditorCommand macro = new EditorCommand() { @Override public void execute() { commands.forEach(EditorCommand::execute); } @Override public void undo() { // 逆序撤销 List<EditorCommand> reversed = new ArrayList<>(commands); Collections.reverse(reversed); reversed.forEach(EditorCommand::undo); } @Override public String getDescription() { return "宏操作 (" + commands.size() + " 步)"; } }; execute(macro); } }

四、命令模式的高级主题

4.1 与事件溯源的结合

java
/** * 事件溯源:命令执行产生领域事件,状态由事件回放重建 */ public class EventSourcedCommandHandler { private final EventStore eventStore; private final EventBus eventBus; public CommandResult handle(FulfillmentCommand command) { // 加载聚合根当前状态 OrderAggregate aggregate = eventStore.replay(command.getOrderId()); // 执行命令,产生领域事件 List<DomainEvent> events = command.executeOn(aggregate); // 持久化事件 long expectedVersion = aggregate.getVersion(); boolean saved = eventStore.append(command.getOrderId(), expectedVersion, events); if (!saved) { // 并发冲突,需重试 return CommandResult.retryable("并发版本冲突", null); } // 发布事件 events.forEach(eventBus::publish); return CommandResult.success(events); } }

五、模式辨析

维度 命令模式 策略模式 备忘录模式
核心意图 请求封装为对象 算法族可互换 状态快照保存
关键能力 延迟执行、撤销、队列 运行时算法替换 状态恢复
与命令模式关系 命令可用策略实现 命令用备忘录实现撤销
适用场景 操作审计、事务、队列 算法选择 状态回滚

命令模式 vs 观察者模式:观察者模式是"一对多"的被动通知,命令模式是"一对一"的主动封装。两者可结合,命令执行后触发事件通知观察者。

六、设计陷阱

陷阱一:命令类爆炸 → 引入命令工厂和泛型参数化,减少子类数量。

陷阱二:补偿逻辑遗漏 → 强制补偿接口,编译期检查,集成测试覆盖。

陷阱三:命令序列化版本兼容 → 使用Schema演进策略,字段增删兼容。

七、结语

命令模式的精髓在于将行为请求提升为一等公民,使其可被存储、传递、编排和回溯。在分布式事务、任务调度、操作审计、多级撤销等场景中,命令模式是实现系统可靠性和可观测性的核心基础设施。掌握命令封装、异步调度、Saga编排、事件溯源等高级形态,警惕类爆炸、补偿遗漏、版本兼容等工程陷阱,是构建企业级履约系统的能力核心。

返回知识中心