设计模式详解-备忘录模式

设计模式详解:备忘录模式

一、模式概述

备忘录模式(Memento Pattern)是行为型设计模式中最具状态管理价值的模式,其核心意图在于在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后恢复。这一模式使对象能够回到过去的某个历史状态,为撤销操作、事务回滚、快照备份、版本控制等场景提供了优雅的解决方案。

备忘录模式的命名源自其功能隐喻——"备忘录"即记录信息以备后查的便条。在软件系统中,这一"便条"是对象状态的完整快照,存储于对象外部,由专门的看管者(Caretaker)管理,而对象本身(Originator)可以在需要时读取备忘录恢复状态。关键在于,备忘录的内容对看管者完全透明,看管者无法也无需理解其内部结构,从而严格保护了对象的封装边界。

备忘录模式的深层价值在于封装与透明性的精妙平衡。直接暴露对象内部状态会破坏封装,违反信息隐藏原则;完全禁止外部访问又使状态持久化、撤销回退成为不可能。备忘录模式通过引入"窄接口"与"宽接口"的分层设计,让看管者只看到备忘录的标识信息(窄接口),而原发器可以访问完整状态(宽接口),实现了安全与灵活的统一。

二、模式结构

备忘录模式包含三个核心角色,形成严格分离的状态管理三角:

原发器(Originator):需要保存状态的对象。负责创建备忘录(捕获当前状态)和使用备忘录(恢复先前状态)。是唯一能够访问备忘录内部状态的角色。

备忘录(Memento):存储原发器内部状态的容器。对原发器提供宽接口(可访问状态),对看管者提供窄接口(仅暴露标识信息,如创建时间、版本号等)。

看管者(Caretaker):负责保存备忘录,但从不操作或检查备忘录内容。可以管理多个备忘录,实现历史记录、撤销栈、分支版本等功能。

经典实现中,备忘录的宽接口通过包级私有或内部类实现,确保只有原发器能够访问。Java等语言中,静态内部类是实现备忘录的常用技术。

三、深度案例:企业级流程编排引擎

以下展示一个真实场景下的备忘录模式应用——支持复杂业务流程的编排引擎,涵盖工作流状态、变量上下文、活动执行历史等多维度状态的快照与恢复。

3.1 原发器:流程实例

java
/** * 原发器:流程实例 * 包含完整的流程执行状态,支持创建和恢复备忘录 */ public class ProcessInstance implements Originator { // ========== 核心状态(需备忘录保存) ========== private String instanceId; private String definitionId; private ProcessStatus status; private String currentActivityId; private Map<String, Variable> variables; private Stack<ActivityExecution> executionStack; private List<ActivityHistory> activityHistory; private Map<String, Timer> activeTimers; private CorrelationContext correlationContext; private CompensationContext compensationContext; // ========== 派生状态(运行时计算,不保存) ========== private transient ProcessDefinition definition; private transient ExecutionEngine engine; private transient EventPublisher eventPublisher; // 构造与业务方法... /** * 创建备忘录:捕获当前完整状态 */ @Override public ProcessMemento createMemento() { // 深拷贝所有可变状态 return ProcessMemento.builder() .instanceId(instanceId) .definitionId(definitionId) .status(status) .currentActivityId(currentActivityId) .variables(deepCopyVariables(variables)) .executionStack(deepCopyStack(executionStack)) .activityHistory(new ArrayList<>(activityHistory)) .activeTimers(new HashMap<>(activeTimers)) .correlationContext(correlationContext.copy()) .compensationContext(compensationContext.copy()) .capturedAt(Instant.now()) .version(calculateVersion()) .build(); } /** * 恢复状态:从备忘录还原 */ @Override public void restoreMemento(ProcessMemento memento) { // 校验备忘录归属 if (!this.instanceId.equals(memento.getInstanceId())) { throw new MismatchMementoException( "备忘录归属不匹配: " + memento.getInstanceId()); } // 校验版本兼容性 if (!isCompatibleVersion(memento.getVersion())) { throw new IncompatibleVersionException( "备忘录版本不兼容: " + memento.getVersion()); } // 恢复状态 this.status = memento.getStatus(); this.currentActivityId = memento.getCurrentActivityId(); this.variables = memento.getVariables(); // 备忘录内部已是深拷贝 this.executionStack = memento.getExecutionStack(); this.activityHistory = memento.getActivityHistory(); this.activeTimers = memento.getActiveTimers(); this.correlationContext = memento.getCorrelationContext(); this.compensationContext = memento.getCompensationContext(); // 重新初始化运行时依赖 reinitializeTransientState(); // 发布恢复事件 eventPublisher.publish(new ProcessRestoredEvent(instanceId, memento.getCapturedAt())); } /** * 执行推进:可能触发状态变更,需保存检查点 */ public ExecutionResult execute(ExecutionCommand command) { // 执行前保存检查点(用于失败回滚) ProcessMemento checkpoint = createMemento(); try { // 执行命令 ActivityExecution current = executionStack.peek(); ExecutionResult result = engine.execute(current, command, variables); // 更新状态 updateState(result); recordHistory(result); // 成功:可选择性保存里程碑 if (result.isMilestone()) { return ExecutionResult.success(result.getOutput(), createMemento()); } return result; } catch (ExecutionException e) { // 失败:回滚到检查点 restoreMemento(checkpoint); throw new ProcessExecutionException("执行失败,已回滚", e); } } // 辅助方法 private Map<String, Variable> deepCopyVariables(Map<String, Variable> source) { Map<String, Variable> copy = new HashMap<>(); source.forEach((k, v) -> copy.put(k, v.copy())); return copy; } private Stack<ActivityExecution> deepCopyStack(Stack<ActivityExecution> source) { Stack<ActivityExecution> copy = new Stack<>(); source.forEach(e -> copy.push(e.copy())); return copy; } private void reinitializeTransientState() { this.definition = DefinitionRegistry.get(definitionId); this.engine = ExecutionEngineFactory.getEngine(definition.getEngineType()); // 重新注册定时器 activeTimers.values().forEach(timer -> TimerScheduler.register(timer)); } }

3.2 备忘录:流程状态快照

java
/** * 备忘录:流程实例状态快照 * 采用内部类实现,严格控制访问权限 */ public final class ProcessMemento { // ========== 窄接口:对看管者可见 ========== private final String mementoId; private final String instanceId; private final Instant capturedAt; private final long version; private final String description; // ========== 宽接口:仅原发器可访问(包级私有) ========== final ProcessStatus status; final String currentActivityId; final Map<String, Variable> variables; final Stack<ActivityExecution> executionStack; final List<ActivityHistory> activityHistory; final Map<String, Timer> activeTimers; final CorrelationContext correlationContext; final CompensationContext compensationContext; // 私有构造器,强制通过Builder创建 private ProcessMemento(Builder builder) { this.mementoId = builder.mementoId; this.instanceId = builder.instanceId; this.capturedAt = builder.capturedAt; this.version = builder.version; this.description = builder.description; this.status = builder.status; this.currentActivityId = builder.currentActivityId; this.variables = builder.variables; this.executionStack = builder.executionStack; this.activityHistory = builder.activityHistory; this.activeTimers = builder.activeTimers; this.correlationContext = builder.correlationContext; this.compensationContext = builder.compensationContext; } // 窄接口方法(public) public String getMementoId() { return mementoId; } public String getInstanceId() { return instanceId; } public Instant getCapturedAt() { return capturedAt; } public long getVersion() { return version; } public String getDescription() { return description; } // 宽接口方法:包级私有,仅同包内的ProcessInstance可访问 ProcessStatus getStatus() { return status; } String getCurrentActivityId() { return currentActivityId; } Map<String, Variable> getVariables() { return variables; } Stack<ActivityExecution> getExecutionStack() { return executionStack; } List<ActivityHistory> getActivityHistory() { return activityHistory; } Map<String, Timer> getActiveTimers() { return activeTimers; } CorrelationContext getCorrelationContext() { return correlationContext; } CompensationContext getCompensationContext() { return compensationContext; } // 序列化支持:用于持久化存储 byte[] serialize() { return MementoSerializer.serialize(this); } static ProcessMemento deserialize(byte[] data) { return MementoSerializer.deserialize(data); } // Builder模式 public static Builder builder() { return new Builder(); } public static class Builder { private String mementoId = UUID.randomUUID().toString(); private String instanceId; private Instant capturedAt; private long version; private String description; private ProcessStatus status; private String currentActivityId; private Map<String, Variable> variables; private Stack<ActivityExecution> executionStack; private List<ActivityHistory> activityHistory; private Map<String, Timer> activeTimers; private CorrelationContext correlationContext; private CompensationContext compensationContext; public Builder instanceId(String id) { this.instanceId = id; return this; } public Builder capturedAt(Instant time) { this.capturedAt = time; return this; } public Builder version(long version) { this.version = version; return this; } public Builder description(String desc) { this.description = desc; return this; } public Builder status(ProcessStatus status) { this.status = status; return this; } public Builder currentActivityId(String id) { this.currentActivityId = id; return this; } public Builder variables(Map<String, Variable> vars) { this.variables = vars; return this; } public Builder executionStack(Stack<ActivityExecution> stack) { this.executionStack = stack; return this; } public Builder activityHistory(List<ActivityHistory> history) { this.activityHistory = history; return this; } public Builder activeTimers(Map<String, Timer> timers) { this.activeTimers = timers; return this; } public Builder correlationContext(CorrelationContext ctx) { this.correlationContext = ctx; return this; } public Builder compensationContext(CompensationContext ctx) { this.compensationContext = ctx; return this; } public ProcessMemento build() { return new ProcessMemento(this); } } }

3.3 看管者:备忘录管理与版本控制

java
/** * 看管者:流程备忘录仓库 * 支持撤销栈、分支版本、时间旅行调试 */ @Component public class ProcessMementoRepository { private final MementoStorage storage; private final MementoIndex index; private final MeterRegistry meterRegistry; // 配置参数 private final int maxUndoDepth; private final Duration retentionPeriod; private final long maxStorageSize; @Autowired public ProcessMementoRepository(MementoStorage storage, MementoIndex index, MeterRegistry meterRegistry, @Value("${memento.maxUndoDepth:50}") int maxUndoDepth, @Value("${memento.retentionDays:30}") int retentionDays, @Value("${memento.maxStorageSize:10737418240}") long maxStorageSize) { this.storage = storage; this.index = index; this.meterRegistry = meterRegistry; this.maxUndoDepth = maxUndoDepth; this.retentionPeriod = Duration.ofDays(retentionDays); this.maxStorageSize = maxStorageSize; } /** * 保存备忘录:自动管理撤销栈深度 */ public void save(ProcessMemento memento) { // 存储备忘录数据 String storageKey = generateStorageKey(memento); storage.store(storageKey, memento.serialize()); // 索引记录 MementoRecord record = MementoRecord.builder() .mementoId(memento.getMementoId()) .instanceId(memento.getInstanceId()) .capturedAt(memento.getCapturedAt()) .version(memento.getVersion()) .storageKey(storageKey) .storageSize(memento.serialize().length) .build(); index.add(record); // 维护撤销栈深度:超出则移除最旧记录 trimUndoStack(memento.getInstanceId()); // 检查存储配额 enforceStorageQuota(); meterRegistry.counter("memento.saved").increment(); } /** * 获取最新备忘录(用于恢复) */ public Optional<ProcessMemento> findLatest(String instanceId) { return index.findLatest(instanceId) .map(this::loadMemento); } /** * 撤销操作:获取上一个状态 */ public Optional<ProcessMemento> undo(String instanceId) { List<MementoRecord> history = index.findByInstanceId(instanceId); if (history.size() < 2) { return Optional.empty(); // 无历史可撤销 } // 标记当前状态为"已撤销" MementoRecord current = history.get(history.size() - 1); index.markUndone(current.getMementoId()); // 返回上一个状态 MementoRecord previous = history.get(history.size() - 2); return Optional.of(loadMemento(previous)); } /** * 重做操作:恢复被撤销的状态 */ public Optional<ProcessMemento> redo(String instanceId) { return index.findFirstUndone(instanceId) .map(this::loadMemento); } /** * 时间旅行:恢复到指定时间点的状态 */ public Optional<ProcessMemento> travelTo(String instanceId, Instant targetTime) { // 找到targetTime之前最新的备忘录 return index.findBefore(instanceId, targetTime) .map(this::loadMemento); } /** * 分支创建:从历史状态创建新分支 */ public String branchFrom(String instanceId, String mementoId, String branchName) { ProcessMemento base = loadMemento(index.findById(mementoId) .orElseThrow(() -> new MementoNotFoundException(mementoId))); // 创建新实例ID String newInstanceId = generateBranchInstanceId(instanceId, branchName); // 修改备忘录归属 ProcessMemento branched = ProcessMemento.builder() .instanceId(newInstanceId) .capturedAt(Instant.now()) .version(1) // 分支版本重置 .description("Branched from " + instanceId + " at " + mementoId) .status(base.status) .currentActivityId(base.currentActivityId) .variables(base.variables) .executionStack(base.executionStack) .activityHistory(new ArrayList<>()) // 分支清空历史 .activeTimers(base.activeTimers) .correlationContext(base.correlationContext.copy()) .compensationContext(base.compensationContext.copy()) .build(); save(branched); return newInstanceId; } /** * 比较两个状态差异(用于调试和审计) */ public MementoDiff compare(String mementoId1, String mementoId2) { ProcessMemento m1 = loadMemento(index.findById(mementoId1) .orElseThrow(() -> new MementoNotFoundException(mementoId1))); ProcessMemento m2 = loadMemento(index.findById(mementoId2) .orElseThrow(() -> new MementoNotFoundException(mementoId2))); return MementoDiff.builder() .variableChanges(diffVariables(m1.getVariables(), m2.getVariables())) .activityChanges(diffActivities(m1.getActivityHistory(), m2.getActivityHistory())) .stackChanges(diffStack(m1.getExecutionStack(), m2.getExecutionStack())) .build(); } /** * 定期清理过期备忘录 */ @Scheduled(cron = "0 0 2 * * ?") // 每日凌晨2点 public void cleanupExpiredMementos() { Instant cutoff = Instant.now().minus(retentionPeriod); List<MementoRecord> expired = index.findBefore(cutoff); for (MementoRecord record : expired) { storage.delete(record.getStorageKey()); index.remove(record.getMementoId()); } meterRegistry.counter("memento.cleaned", "count", String.valueOf(expired.size())).increment(); } // ========== 私有辅助方法 ========== private ProcessMemento loadMemento(MementoRecord record) { byte[] data = storage.retrieve(record.getStorageKey()); return ProcessMemento.deserialize(data); } private void trimUndoStack(String instanceId) { List<MementoRecord> history = index.findByInstanceId(instanceId); while (history.size() > maxUndoDepth) { MementoRecord oldest = history.remove(0); storage.delete(oldest.getStorageKey()); index.remove(oldest.getMementoId()); } } private void enforceStorageQuota() { long currentSize = index.getTotalStorageSize(); if (currentSize > maxStorageSize) { // 按LRU淘汰最旧备忘录 List<MementoRecord> lruRecords = index.findLRU( (int) ((currentSize - maxStorageSize) / averageMementoSize())); for (MementoRecord record : lruRecords) { storage.delete(record.getStorageKey()); index.remove(record.getMementoId()); } } } private String generateStorageKey(ProcessMemento memento) { return String.format("memento/%s/%s_%s.dat", memento.getInstanceId(), memento.getCapturedAt().toEpochMilli(), memento.getMementoId()); } private String generateBranchInstanceId(String baseInstanceId, String branchName) { return baseInstanceId + "-branch-" + branchName + "-" + UUID.randomUUID().toString().substring(0, 8); } }

3.4 事务协调:分布式场景下的备忘录

java
/** * 分布式事务协调器:使用备忘录实现Saga的补偿 */ @Component public class SagaTransactionCoordinator { private final ProcessMementoRepository mementoRepository; private final CompensationExecutor compensationExecutor; /** * 执行Saga事务:每个步骤保存检查点,失败时按备忘录补偿 */ public SagaResult executeSaga(SagaDefinition saga, Map<String, Object> input) { String sagaId = generateSagaId(); SagaContext context = new SagaContext(sagaId, input); // 初始备忘录 ProcessMemento initialCheckpoint = createSagaMemento(context, 0); mementoRepository.save(initialCheckpoint); List<SagaStep> steps = saga.getSteps(); for (int i = 0; i < steps.size(); i++) { SagaStep step = steps.get(i); try { // 执行步骤 StepResult result = executeStep(step, context); context.recordStepResult(step.getStepId(), result); // 保存前进检查点 ProcessMemento forwardCheckpoint = createSagaMemento(context, i + 1); mementoRepository.save(forwardCheckpoint); } catch (StepExecutionException e) { // 步骤失败:回滚到上一个检查点,执行补偿 context.setFailedStep(step.getStepId()); context.setFailureReason(e.getMessage()); // 加载上一个成功状态 Optional<ProcessMemento> previous = mementoRepository.undo(sagaId); if (previous.isPresent()) { SagaContext previousContext = restoreSagaContext(previous.get()); // 执行补偿 List<CompensationResult> compensations = new ArrayList<>(); for (int j = i - 1; j >= 0; j--) { CompensationResult comp = compensationExecutor.execute( steps.get(j).getCompensation(), previousContext); compensations.add(comp); } return SagaResult.compensated(sagaId, compensations); } return SagaResult.failed(sagaId, e.getMessage()); } } return SagaResult.success(sagaId); } /** * 创建Saga专用的备忘录 */ private ProcessMemento createSagaMemento(SagaContext context, int completedSteps) { return ProcessMemento.builder() .instanceId(context.getSagaId()) .capturedAt(Instant.now()) .version(completedSteps) .description("Saga checkpoint after step " + completedSteps) .status(completedSteps == context.getTotalSteps() ? ProcessStatus.COMPLETED : ProcessStatus.RUNNING) .variables(context.getVariables()) .executionStack(context.getExecutionStack()) .activityHistory(context.getCompletedActivities()) .activeTimers(context.getActiveTimers()) .correlationContext(context.getCorrelationContext()) .compensationContext(context.getCompensationContext()) .build(); } }

四、备忘录模式的高级主题

4.1 增量备忘录:优化大对象存储

java
/** * 增量备忘录:仅存储变更部分,减少存储开销 */ public class IncrementalProcessMemento implements Memento { private final String baseMementoId; // 基准完整快照 private final Map<String, VariableDelta> variableDeltas; private final List<ActivityHistory> newActivities; private final StackDelta executionStackDelta; // 恢复时:加载基准快照 + 应用增量 public ProcessState restore(ProcessMementoRepository repository) { ProcessMemento base = repository.findById(baseMementoId) .orElseThrow(() -> new MementoNotFoundException(baseMementoId)); ProcessState state = base.restore(); // 应用变量增量 variableDeltas.forEach((key, delta) -> { state.applyVariableDelta(key, delta); }); // 追加新活动 state.getActivityHistory().addAll(newActivities); // 应用栈增量 executionStackDelta.applyTo(state.getExecutionStack()); return state; } }

4.2 备忘录与事件溯源的融合

java
/** * 事件溯源视角:备忘录作为状态投影的快照 */ public class EventSourcedProcessInstance implements Originator { private final EventStore eventStore; private long lastSnapshotVersion; @Override public ProcessMemento createMemento() { // 从事件流重建状态后创建快照 replayEvents(); return createMementoFromCurrentState(); } @Override public void restoreMemento(ProcessMemento memento) { // 设置快照版本,后续从该版本后的事件继续重建 this.lastSnapshotVersion = memento.getVersion(); super.restoreMemento(memento); } /** * 优化加载:先加载最近快照,再重放后续事件 */ public void loadOptimized(String instanceId) { // 1. 查找最新快照 Optional<ProcessMemento> snapshot = mementoRepository.findLatest(instanceId); if (snapshot.isPresent()) { // 2. 恢复快照 restoreMemento(snapshot.get()); // 3. 重放快照后的事件 List<DomainEvent> subsequentEvents = eventStore.readFrom( instanceId, snapshot.get().getVersion()); subsequentEvents.forEach(this::apply); } else { // 无快照:全量重放 List<DomainEvent> allEvents = eventStore.readAll(instanceId); allEvents.forEach(this::apply); } } }

五、备忘录模式与相关模式的辨析

备忘录 vs 原型模式:备忘录捕获状态用于恢复,原型复制对象用于创建新实例。备忘录关注时间维度上的状态回溯,原型关注空间维度上的对象复制。

备忘录 vs 状态模式:状态模式封装状态转换的行为,备忘录模式存储状态本身。二者可结合:状态模式管理状态转换,备忘录模式保存状态历史。

备忘录 vs 命令模式:命令模式封装操作以便撤销,备忘录模式封装状态以便恢复。命令模式的撤销通过执行反向操作实现,备忘录模式的恢复通过状态替换实现。命令模式适合操作粒度粗、状态简单的场景,备忘录模式适合状态复杂、需要完整快照的场景。

六、设计陷阱与规避策略

陷阱一:备忘录体积过大

复杂对象的状态快照可能非常庞大。解决方案:增量存储、延迟加载、压缩序列化、或仅保存关键状态。

陷阱二:深拷贝性能损耗

创建备忘录时的深拷贝可能耗时。解决方案:使用写时复制(Copy-on-Write)、不可变数据结构、或增量备忘录。

陷阱三:状态版本兼容性

对象结构演化后,旧备忘录可能无法恢复。解决方案:版本迁移策略、序列化格式的前向兼容、或备忘录升级服务。

陷阱四:并发修改冲突

备忘录创建过程中对象被修改。解决方案:快照隔离、乐观锁、或创建备忘录时加锁。

七、结语

备忘录模式是状态管理领域的基石,它以精巧的封装设计,在不破坏对象完整性的前提下,实现了状态的捕获、存储与恢复。在流程引擎、事务协调、版本控制、调试工具等场景中,备忘录模式是不可或缺的基础设施。理解其窄接口与宽接口的分层设计,掌握增量优化、事件溯源融合等高级技术,警惕体积膨胀与版本兼容等工程陷阱,是构建可靠状态管理系统的能力核心。备忘录模式的精髓在于对时间的尊重——承认状态会变化,变化会出错,出错需要回溯,而回溯必须安全。

返回知识中心