摘要
備忘錄設計模式是一種行為型設計模式,用于在不破壞封裝性的前提下,捕獲對象的內部狀態并在需要時恢復。它包含三個關鍵角色:原發器(Originator)、備忘錄(Memento)和負責人(Caretaker)。該模式的優點包括保留對象狀態、支持回滾和易于實現撤銷/重做功能,但缺點是狀態快照可能占用大量內存且管理復雜。其結構可通過嵌套類或中間接口類圖表示,實現方式涉及原發器創建和恢復備忘錄、備忘錄存儲狀態、負責人保存和獲取備忘錄。適合用于需要撤銷/重做功能或頻繁保存狀態的場景,但當狀態變化不頻繁或內存受限時則不適合。實戰示例包括數據庫腳本、實體類和狀態保存與撤銷API。此外,該模式可與其他設計模式或技術結合使用,如狀態機模式、命令模式、責任鏈模式、原型模式、觀察者模式、策略模式、持久化機制和Spring AOP/注解。
1. 備忘錄設計模式定義
備忘錄設計模式(Memento Pattern) 是一種行為型設計模式,用于在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在以后需要時將其恢復到原先的狀態。
1.1. ? 關鍵角色
角色 | 說明 |
Originator | 原發器,擁有內部狀態,需要保存快照并恢復自身狀態 |
Memento | 備忘錄,存儲 |
Caretaker | 負責人,管理備忘錄的保存與恢復,但不訪問其內容 |
1.2. 優點:
- 保留對象狀態,支持回滾
- 不破壞封裝(狀態通過 Memento 管理)
- 易于實現撤銷/重做功能
1.3. 缺點:
- 狀態快照可能占用大量內存
- Caretaker 可能需要管理多個 Memento,帶來管理復雜性
2. 備忘錄設計模式結構
2.1. 基于嵌套類類圖
2.2. 基于中間接口類圖
2.3. 備忘錄時序圖
3. 備忘錄設計模式實現方式
備忘錄設計模式(Memento Pattern)實現方式的核心在于:在不破壞對象封裝性的前提下,捕獲并保存對象的內部狀態,以便之后可以將其恢復。
3.1. 🧩 Originator(原發器):擁有狀態,負責創建和恢復備忘錄
public class Originator {private String state; // 需要保存的內部狀態public void setState(String state) {this.state = state;System.out.println("設置狀態為:" + state);}public String getState() {return state;}// 創建備忘錄public Memento saveStateToMemento() {return new Memento(state);}// 從備忘錄恢復狀態public void restoreStateFromMemento(Memento memento) {this.state = memento.getState();System.out.println("恢復狀態為:" + state);}
}
3.2. 🧩 Memento(備忘錄):只暴露給 Originator,用于存儲狀態
public class Memento {private final String state;public Memento(String state) {this.state = state;}// 只有 Originator 使用protected String getState() {return state;}
}
3.3. 🧩 Caretaker(管理者):負責保存、獲取備忘錄,但不操作內容
import java.util.ArrayList;
import java.util.List;public class Caretaker {private List<Memento> mementoList = new ArrayList<>();// 添加備忘錄public void add(Memento memento) {mementoList.add(memento);}// 獲取某個備忘錄public Memento get(int index) {return mementoList.get(index);}
}
3.4. 🚀 使用示例(模擬狀態保存與恢復)
public class Client {public static void main(String[] args) {Originator originator = new Originator();Caretaker caretaker = new Caretaker();originator.setState("狀態 #1");originator.setState("狀態 #2");caretaker.add(originator.saveStateToMemento()); // 保存狀態2originator.setState("狀態 #3");caretaker.add(originator.saveStateToMemento()); // 保存狀態3originator.setState("狀態 #4");// 恢復狀態originator.restoreStateFromMemento(caretaker.get(0)); // 恢復到狀態2originator.restoreStateFromMemento(caretaker.get(1)); // 恢復到狀態3}
}
3.5. ? 備忘錄總結
組件 | 職責 |
Originator | 負責創建和恢復備忘錄 |
Memento | 存儲狀態(不可變對象) |
Caretaker | 管理多個備忘錄(可以是棧、隊列、列表) |
3.6. ? 延伸使用(如 Spring 項目中)
你可以將備忘錄模式與:
- Spring 注解(如
@Service
、@Component
) - 數據持久化(備忘錄入庫,支持長期存檔)
- REST 接口撤銷(如風控規則撤銷)
- 狀態機結合(狀態保存+回滾)
4. 備忘錄設計模式適合場景
4.1. ? 適合使用備忘錄模式的場景
場景 | 說明 |
? 撤銷操作(Undo/Redo)功能 | 比如文本編輯器、IDE、畫圖工具,用戶希望能夠一步步撤銷操作。 |
? 狀態快照與回滾 | 比如事務性系統、工作流、審批流、游戲存檔等,可以保存當前狀態,失敗時快速回滾。 |
? 風控策略、配置管理 | 在金融風控系統中,策略配置改動后,能夠恢復到某個時間點前的策略狀態。 |
? 狀態機狀態保存 | 和狀態機結合使用,記錄每個狀態變化的歷史,可實現狀態追溯。 |
? 臨時修改但可還原的場景 | 比如購物車中的臨時優惠應用、試算試驗。 |
? AI / 數據建模模擬 | 在做一系列模擬實驗時,需要保存中間狀態,方便比較或回退。 |
4.2. ? 不適合使用備忘錄模式的場景
場景 | 原因 |
? 狀態對象非常龐大或頻繁變動 | 會頻繁創建大量備份,造成內存/存儲負擔。例如大型圖像、視頻編輯。 |
? 備份數據無法或不允許暴露給外部 | 即使模式保證封裝性,有些敏感狀態如密鑰/隱私也不應被存儲。 |
? 狀態之間無明顯斷點或快照意義不大 | 比如高頻實時流系統,數據快速變化,快照意義小。 |
? 備份狀態對業務無價值 | 若狀態回滾從不發生,記錄也無實際意義,反而增加維護成本。 |
? 替代機制更適合 | 比如用數據庫的事務機制或版本控制系統就能解決的,不必使用設計模式實現復雜備份。 |
總結:備忘錄模式適用于“狀態可保存且可能需要還原”的對象,在撤銷、版本控制、配置管理等場景特別合適。
5. 備忘錄設計模式實戰示例
在風控系統中,用戶可配置規則,每次變更都自動保存歷史狀態,并支持「撤銷」功能。
5.1. ?? 數據庫腳本(rule_history.sql
)
CREATE TABLE rule_config (id BIGINT PRIMARY KEY AUTO_INCREMENT,rule_code VARCHAR(64),rule_content TEXT,version INT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);CREATE TABLE rule_memento (id BIGINT PRIMARY KEY AUTO_INCREMENT,rule_id BIGINT,rule_content TEXT,version INT,saved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
5.2. ?? 實體類
@Data
@Entity
@Table(name = "rule_config")
public class RuleConfig {@Id@GeneratedValueprivate Long id;private String ruleCode;private String ruleContent;private Integer version;
}
@Data
@Entity
@Table(name = "rule_memento")
public class RuleMemento {@Id@GeneratedValueprivate Long id;private Long ruleId;private String ruleContent;private Integer version;private Timestamp savedAt;
}
5.3. 🧠 備忘錄模式結構
5.3.1. 備忘錄(Memento)
public class RuleMementoSnapshot {private final String content;private final Integer version;public RuleMementoSnapshot(String content, Integer version) {this.content = content;this.version = version;}public String getContent() {return content;}public Integer getVersion() {return version;}
}
5.3.2. 發起人(Originator)
@Component
public class RuleOriginator {public RuleMementoSnapshot save(RuleConfig ruleConfig) {return new RuleMementoSnapshot(ruleConfig.getRuleContent(), ruleConfig.getVersion());}public void restore(RuleConfig ruleConfig, RuleMementoSnapshot snapshot) {ruleConfig.setRuleContent(snapshot.getContent());ruleConfig.setVersion(snapshot.getVersion());}
}
5.3.3. 負責人(Caretaker)
@Service
public class RuleHistoryService {@Autowiredprivate RuleMementoRepository mementoRepository;public void saveMemento(Long ruleId, RuleMementoSnapshot snapshot) {RuleMemento memento = new RuleMemento();memento.setRuleId(ruleId);memento.setRuleContent(snapshot.getContent());memento.setVersion(snapshot.getVersion());mementoRepository.save(memento);}public RuleMementoSnapshot getLastMemento(Long ruleId) {RuleMemento latest = mementoRepository.findTopByRuleIdOrderBySavedAtDesc(ruleId);return new RuleMementoSnapshot(latest.getRuleContent(), latest.getVersion());}
}
5.4. 💡 狀態保存與撤銷 API(Controller)
@RestController
@RequestMapping("/api/rule")
public class RuleController {@Autowired private RuleRepository ruleRepository;@Autowired private RuleHistoryService historyService;@Autowired private RuleOriginator originator;@PostMapping("/update")public ResponseEntity<String> updateRule(@RequestBody RuleConfig newRule) {RuleConfig old = ruleRepository.findById(newRule.getId()).orElseThrow();RuleMementoSnapshot snapshot = originator.save(old);historyService.saveMemento(old.getId(), snapshot);old.setRuleContent(newRule.getRuleContent());old.setVersion(old.getVersion() + 1);ruleRepository.save(old);return ResponseEntity.ok("規則已更新并記錄歷史");}@PostMapping("/undo/{ruleId}")public ResponseEntity<String> undo(@PathVariable Long ruleId) {RuleConfig rule = ruleRepository.findById(ruleId).orElseThrow();RuleMementoSnapshot lastSnapshot = historyService.getLastMemento(ruleId);originator.restore(rule, lastSnapshot);ruleRepository.save(rule);return ResponseEntity.ok("回滾成功");}
}
6. 備忘錄設計模式思考
備忘錄設計模式(Memento Pattern)在實際開發中往往不會單獨使用,而是與其他設計模式組合,以解決更復雜的狀態管理、回滾、撤銷等需求。下面是常見的組合方式和典型應用場景,尤其適合 金融風控、流程引擎、配置管理 等場景。
6.1. ? 備忘錄模式 + 狀態機模式(State Pattern)
組合說明:
- 狀態機負責管理狀態切換邏輯;
- 備忘錄負責保存每個狀態快照,實現狀態回滾/撤銷。
場景示例:
- 風控審批流程中,支持將任務狀態退回上一步。
- 訂單狀態(待支付 → 已支付 → 配送中)中用戶申請退款,需要回退到「待支付」。
6.2. ? 備忘錄模式 + 命令模式(Command Pattern)
組合說明:
- 命令封裝用戶操作;
- 備忘錄記錄執行前的狀態,實現
undo()
操作。
場景示例:
- 金融交易撤銷;
- 系統管理員修改風控規則,每次操作都可以回滾。
6.3. ? 備忘錄模式 + 責任鏈模式(Chain of Responsibility)
組合說明:
- 每個處理節點執行任務前記錄狀態;
- 執行失敗時利用備忘錄回滾上一步處理。
場景示例:
- 信貸審批流程,每個環節出錯需恢復上一次狀態。
6.4. ? 備忘錄模式 + 原型模式(Prototype Pattern)
組合說明:
- 使用原型的淺/深拷貝方式快速創建備忘錄對象;
- 提高狀態快照的創建效率。
場景示例:
- 在風險規則配置頁面修改參數時,使用深克隆構建快照并保存。
6.5. ? 備忘錄模式 + 觀察者模式(Observer Pattern)
組合說明:
- 狀態變化時通知觀察者;
- 備忘錄用于記錄狀態變化歷史。
場景示例:
- 配置變更通知下游服務,但允許撤銷恢復上一個配置。
6.6. ? 備忘錄模式 + 策略模式(Strategy Pattern)
組合說明:
- 策略負責計算/處理;
- 備忘錄記錄策略執行前后的狀態,便于結果回退。
場景示例:
- 多種風控策略組合決策后,若發現誤判,恢復上一次執行前的狀態。
6.7. ? 備忘錄模式 + 持久化機制(如 Repository、數據庫)
組合說明:
- 備忘錄對象不是只存在于內存,而是持久化保存(如 JSON 存庫)。
- 支持跨進程或長時間狀態恢復。
場景示例:
- 某條風控規則上線后的歷史版本備份,可通過后臺 UI 操作恢復。
6.8. ? 備忘錄模式 + Spring AOP/注解
組合說明:
- 使用注解(如
@MementoBackup
)自動攔截方法; - 在方法前后生成并記錄狀態快照,實現零侵入式回滾機制。
場景示例:
- Spring Boot 應用中,支持風控參數調整回滾功能,只需加注解即可。
6.9. 🧩 組合參考表
組合模式 | 典型作用 | 適用系統類型 |
狀態機 | 管理狀態轉換+回滾 | 審批系統、風控流程引擎 |
命令 | 操作封裝+撤銷 | 配置平臺、策略決策平臺 |
責任鏈 | 多環節狀態保護 | 風控引擎、流程引擎 |
原型 | 快速復制狀態 | 配置回滾、草稿管理 |
觀察者 | 狀態廣播+恢復 | 分布式配置中心 |
策略 | 策略組合+狀態對比 | 風控策略、限額控制 |
持久化 | 狀態歷史存檔 | 長期任務跟蹤系統 |
AOP/注解 | 自動化攔截/回滾 | 企業級 Spring 應用 |