设计模式详解-备忘录模式
设计模式详解:备忘录模式
一、模式概述
备忘录模式(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)、不可变数据结构、或增量备忘录。
陷阱三:状态版本兼容性
对象结构演化后,旧备忘录可能无法恢复。解决方案:版本迁移策略、序列化格式的前向兼容、或备忘录升级服务。
陷阱四:并发修改冲突
备忘录创建过程中对象被修改。解决方案:快照隔离、乐观锁、或创建备忘录时加锁。
七、结语
备忘录模式是状态管理领域的基石,它以精巧的封装设计,在不破坏对象完整性的前提下,实现了状态的捕获、存储与恢复。在流程引擎、事务协调、版本控制、调试工具等场景中,备忘录模式是不可或缺的基础设施。理解其窄接口与宽接口的分层设计,掌握增量优化、事件溯源融合等高级技术,警惕体积膨胀与版本兼容等工程陷阱,是构建可靠状态管理系统的能力核心。备忘录模式的精髓在于对时间的尊重——承认状态会变化,变化会出错,出错需要回溯,而回溯必须安全。