设计模式详解-命令模式
设计模式详解:命令模式
一、模式概述
命令模式(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编排、事件溯源等高级形态,警惕类爆炸、补偿遗漏、版本兼容等工程陷阱,是构建企业级履约系统的能力核心。