[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-21":26,"\u002Farticles\u002Fdesign-pattern-21\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-21\u002Fadjacent":70,"\u002Fcategories":81,"render-design-pattern-21":107},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113934123146940417","design-pattern-21","设计模式修炼之路 ㉑ 备忘录模式——不破坏封装的存档术","不破坏封装的存档术、宽窄接口、Git 与 Savepoint、命令+备忘录合订","2113897628365230081",1,3711,6474,17,"2024-10-08T16:37:17","> 行为型第八站。\n\n⑱ 命令模式讲撤销时我们走了\"逆操作\"路线：撤销插入就是删除、撤销删除就是插回。轻巧，但有个前提——**每个操作都必须可逆**。\n\n游戏角色可没有\"逆操作\"：血量、蓝量、金币、装备、坐标……打 Boss 团灭了，想把角色恢复到进本之前，怎么办？难道把所有字段 getter\u002Fsetter 全开放，让存档代码自己抄一份？——那封装就死透了，而且外挂狂喜。\n\n备忘录模式就是为这个困境而生的：**不破坏封装的前提下，把内部状态存下来、再原样恢复。**\n\n## 一、备忘录模式是什么\n\nGoF 的原始定义：\n\n> Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.\n> （在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便以后需要时将该对象恢复到原先保存的状态。）\n\n定义第一句就是\"不破坏封装\"——这是备忘录模式和\"暴露字段随便抄\"的分界线，也是它全部技巧所在。\n\n三个角色：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Originator（原发器）\u003C\u002Ftd>\u003Ctd>状态的主人：打包快照、从快照恢复\u003C\u002Ftd>\u003Ctd>GameRole\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Memento（备忘录）\u003C\u002Ftd>\u003Ctd>状态快照的容器：对外是个黑盒\u003C\u002Ftd>\u003Ctd>RoleMemento\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Caretaker（管理者）\u003C\u002Ftd>\u003Ctd>保管备忘录：只存不读、只搬运不拆箱\u003C\u002Ftd>\u003Ctd>RoleStateCaretaker\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n这个模式解的是一个\"既要又要\"的矛盾：**存档必须读取角色的状态（不然存什么），恢复必须写回角色的状态（不然怎么还原）——但一旦把读写开放出去，封装就没了。**\n\n解法是釜底抽薪：**打包和恢复这两个动作，都由角色亲自动手。**外界全程只搬运一个黑盒，从头到尾没碰到任何字段。类比游戏存档：存档时是你自己（角色）把状态写进存档卡，读档时也是你自己照着卡恢复——仓库管理员只负责把卡收好，他根本看不懂卡上写的是什么（也不想看懂）。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **封装完好无损**。外界拿不到字段，连存档器自己都读不懂快照内容——角色加十个字段，存档器一行代码不用改。\n2. **状态恢复的完整性有保证**。快照是角色亲手打包的，里面装了什么、怎么还原，只有角色知道，不会出现\"漏抄一个字段\"的残废存档。\n3. **职责干净**。角色管打包还原，管理器管保管调度，各自单一职责。\n4. **快照可复用**。存档槽可以做成列表——多存档位、自动存档、时间机器，都是管理者层面的小扩展。\n\n**缺点：**\n\n1. **内存开销**。每份快照都是一份状态的完整拷贝，状态一大多存几份，内存就顶不住（⑱ 反面教材 BadEditor 的快照栈就是教训——这是\"高频快照 + 大状态\"的组合陷阱）。\n2. **黑盒的代价**：管理者想展示\"存档详情\"给用户看？做不到，内容被锁死了。需要展示就得额外开口子，封装的口子一开就收不回。\n3. **类数量增加**。每个需要存档的原发器都得配一个备忘录类。\n4. **只适合\"全量快照\"语义**：想只撤销\"半步\"（只回滚血量不回滚金币），备忘录原生不支持——粒度问题要靠拆多个备忘录类解决。\n\n## 三、什么时候用\n\n判断标准：**需要把对象状态恢复到过去某个时点，且对象状态复杂\u002F含私有细节，不能或不该用\"逆操作\"来表达。**\n\n- **游戏存档**：角色状态、地图进度、任务进度——本文场景\n- **事务保存点**：数据库 `SAVEPOINT` \u002F `ROLLBACK TO`，长事务打点回滚\n- **编辑器\u002FIDE 状态恢复**：窗口布局、断点集、打开的文件列表\n- **版本控制**：每次 commit 都是一次备忘录\n- **草稿箱\u002F定时快照**：表单自动保存、图文编辑器的历史版本\n\n反面对照：操作可逆且高频（文本编辑的打字删除）——用 ⑱ 命令模式的增量撤销更轻；只是想跨进程传输对象——那是序列化，不需要\"黑盒\"；状态就两三个字段——直接 copy 个 record 就完事了。\n\n## 四、实现方式：游戏角色的存档与读档\n\n### 4.1 反面教材：字段裸奔\n\n先看最直觉的存档做法——把字段全开放出去，让存档代码自己抄：\n\n```java\npublic class BadGameRole {\n\n    private int hp = 100;\n    private int mp = 80;\n    private int gold = 500;\n\n    public void fightBoss() {\n        hp = 10;\n        mp = 20;\n        gold = 1200;\n    }\n\n    public void display(String tag) {\n        System.out.println(\"  [角色] \" + tag + \"：HP=\" + hp + \", MP=\" + mp + \", 金币=\" + gold);\n    }\n\n    \u002F\u002F 为了让外界能存档\u002F读档，被迫开放所有字段的读写\n    \u002F\u002F 代价一：存档代码必须认识每个字段，角色以后加字段它就漏抄\n    \u002F\u002F 代价二：任何代码都能 setHp(99999)——外挂就是这么诞生的\n    public int getHp() { return hp; }\n    public void setHp(int hp) { this.hp = hp; }\n    public int getMp() { return mp; }\n    public void setMp(int mp) { this.mp = mp; }\n    public int getGold() { return gold; }\n    public void setGold(int gold) { this.gold = gold; }\n}\n```\n\n存档方\"人肉抄写\"：\n\n```java\nint savedHp = bad.getHp();\nint savedMp = bad.getMp();\nint savedGold = bad.getGold();\n\nbad.fightBoss();\n\nbad.setHp(savedHp);\nbad.setMp(savedMp);\nbad.setGold(savedGold);\n```\n\n功能能用，三宗罪却在输出里现了形：存档代码必须逐字段认识角色（角色加个\"装备列表\"，这里必漏）；恢复靠人肉抄写（抄错一个字段，存档就是残废）；setter 全公开——`setHp(99999)` 一行，外挂诞生。\n\n### 4.2 正解：角色亲手打包，管理者只管搬运\n\n第一步，原发器 + 备忘录。关键设计：**RoleMemento 做成 GameRole 的静态内部类，字段和构造全是 private**——嵌套类之间可以互访私有成员，但外部类（包括管理者和任何代码）统统摸不到：\n\n```java\n\u002F\u002F 原发器：拥有需要保存的状态，自己负责\"打包快照\"和\"从快照恢复\"\npublic class GameRole {\n\n    private int hp = 100;\n    private int mp = 80;\n    private int gold = 500;\n\n    public void fightBoss() {\n        hp = 10;      \u002F\u002F 打得很惨\n        mp = 20;\n        gold = 1200;  \u002F\u002F 但捡了点钱\n    }\n\n    \u002F\u002F 打包当前状态：只有自己能创建和读取备忘录\n    public RoleMemento createMemento() {\n        return new RoleMemento(hp, mp, gold);\n    }\n\n    \u002F\u002F 从快照恢复：只有自己读得到备忘录里的字段\n    public void restoreMemento(RoleMemento memento) {\n        this.hp = memento.hp;\n        this.mp = memento.mp;\n        this.gold = memento.gold;\n    }\n\n    public void display(String tag) {\n        System.out.println(\"  [角色] \" + tag + \"：HP=\" + hp + \", MP=\" + mp + \", 金币=\" + gold);\n    }\n\n    \u002F\u002F 备忘录：静态内部类，字段 private、构造 private\n    \u002F\u002F 管理者和外界拿在手里也只是个黑盒——看得见对象，摸不到内容\n    public static class RoleMemento {\n\n        private final int hp;\n        private final int mp;\n        private final int gold;\n\n        private RoleMemento(int hp, int mp, int gold) {\n            this.hp = hp;\n            this.mp = mp;\n            this.gold = gold;\n        }\n\n        @Override\n        public String toString() {\n            return \"快照 [HP=\" + hp + \", MP=\" + mp + \", 金币=\" + gold + \"]\";\n        }\n    }\n}\n```\n\n第二步，管理者。它干的就是存和还，内容与它无关：\n\n```java\n\u002F\u002F 管理者：只负责保管和交还备忘录，不看内容、不乱动\n\u002F\u002F 它持有 RoleMemento 引用，但字段和构造全是 private——想读想改都没门\npublic class RoleStateCaretaker {\n\n    private GameRole.RoleMemento memento;\n\n    public void saveMemento(GameRole.RoleMemento memento) {\n        this.memento = memento;\n        System.out.println(\"  [管理器] 存档完成：\" + memento);\n    }\n\n    public GameRole.RoleMemento retrieveMemento() {\n        System.out.println(\"  [管理器] 交还存档：\" + memento);\n        return memento;\n    }\n}\n```\n\n第三步，测试——存档、打 Boss、团灭、读档：\n\n```java\npublic class MementoTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：字段裸奔 ==========\");\n        BadGameRole bad = new BadGameRole();\n        bad.display(\"战斗前\");\n\n        int savedHp = bad.getHp();\n        int savedMp = bad.getMp();\n        int savedGold = bad.getGold();\n\n        bad.fightBoss();\n        bad.display(\"战斗后\");\n\n        bad.setHp(savedHp);\n        bad.setMp(savedMp);\n        bad.setGold(savedGold);\n        bad.display(\"人肉读档\");\n\n        bad.setHp(99999);\n        bad.display(\"外挂之后\");\n\n        System.out.println();\n        System.out.println(\"========== 正解：备忘录模式 ==========\");\n        GameRole role = new GameRole();\n        RoleStateCaretaker caretaker = new RoleStateCaretaker();\n\n        role.display(\"战斗前\");\n        caretaker.saveMemento(role.createMemento());\n\n        role.fightBoss();\n        role.display(\"战斗后\");\n\n        System.out.println(\"  [剧情] 团灭了，读档重打——\");\n        role.restoreMemento(caretaker.retrieveMemento());\n        role.display(\"读档后\");\n    }\n}\n```\n\n运行输出：\n\n```text\n========== 反面教材：字段裸奔 ==========\n  [角色] 战斗前：HP=100, MP=80, 金币=500\n  [角色] 战斗后：HP=10, MP=20, 金币=1200\n  [角色] 人肉读档：HP=100, MP=80, 金币=500\n  [角色] 外挂之后：HP=99999, MP=80, 金币=500\n\n========== 正解：备忘录模式 ==========\n  [角色] 战斗前：HP=100, MP=80, 金币=500\n  [管理器] 存档完成：快照 [HP=100, MP=80, 金币=500]\n  [角色] 战斗后：HP=10, MP=20, 金币=1200\n  [剧情] 团灭了，读档重打——\n  [管理器] 交还存档：快照 [HP=100, MP=80, 金币=500]\n  [角色] 读档后：HP=100, MP=80, 金币=500\n```\n\n### 4.3 三处细节咂摸一下\n\n**细节一：打包和恢复都是角色亲手做的。**createMemento 里读字段、restoreMemento 里写字段的代码都在 GameRole 内部——外界从头到尾没接触过任何字段。所以角色以后加\"装备列表\"\"坐标\"\"Buff 集合\"，只需要改这两个方法，管理者和所有调用方零改动。对比反面教材\"加字段 → 全世界的存档代码跟着改\"，这就是\"封装守住了\"的实际含义。\n\n**细节二：静态内部类 + private，是\"宽窄接口\"的 Java 惯用实现。**备忘录模式的理论里有个双接口概念：Originator 拿宽接口（读写全部内容），Caretaker 拿窄接口（只能持有引用）。Java 里不需要真写两个接口——嵌套类天然互访私有成员（编译器会合成桥方法），而外部任何类都摸不到 private。于是同一个 RoleMemento，在 GameRole 眼里是透明的，在 Caretaker 眼里是黑盒。\"看得见摸不着\"，编译器站岗。\n\n**细节三：管理者为什么必须\"不读内容\"？**不是技术不能，是制度不许。Caretaker 的职责是\"什么时候存、存多久、存哪个槽位\"——一旦它开始读快照内容做展示、做判断，它就懂了业务，黑盒就废了：角色改字段，管理器也要跟着改。窄接口不是能力限制，是防腐层。真要展示存档信息？让 GameRole 提供 `describe(Memento)` 之类的受控方法，绕回角色自己身上。\n\n## 五、在框架里找它\n\n**Git——世界上最大的备忘录系统。**每次 commit 是一次 createMemento（把工作区状态打包成不可变的快照对象），`.git` 仓库是 Caretaker（只负责存和取，快照内容按内容寻址、谁也别想手改），`git restore` \u002F `git checkout` 是 restoreMemento。\"写代码前先 commit 一版\"和\"打 Boss 前先存个档\"，是同一个求生智慧。\n\n**JDBC Savepoint——数据库事务的存档点。**长事务里 `Connection.setSavepoint(\"sp1\")` 打一个存档点，后面操作出错 `conn.rollback(sp1)` 只回滚到打点处、不毁掉之前的成果——正是\"多存档位 + 定点读档\"的工业级实现。打点就是快照，回滚就是恢复。\n\n**IDE 本地历史。**IntelliJ 的 Local History、VS Code 的 Timeline：你每次保存、甚至每次失去焦点，IDE 都悄悄存一份文件快照——误删了代码、refactor 翻车了，右键 Local History 一秒回到五分钟前。这类功能敢做成\"全自动\"，底气就是快照的打包\u002F恢复全在 IDE 内部闭环，外界（包括你）只负责挑时间点。\n\n**浏览器后退栈、操作系统的系统还原点、手机云备份**——所有\"回到过去\"的按钮，背后都是同一个模式。\n\n一句话总结这一节：**凡是\"把现在存起来，以便将来后悔\"的地方，备忘录模式都在默默服务。**\n\n## 六、撤销问题的完整拼图：命令 + 备忘录\n\n至此，\"后悔药\"的两种配方集齐了，合上账本：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>命令模式 ⑱\u003C\u002Fth>\u003Cth>备忘录模式 ㉑\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>撤销原理\u003C\u002Ftd>\u003Ctd>逆操作回放（撤销插入=删除）\u003C\u002Ftd>\u003Ctd>快照回滚（整个状态倒回）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>存储\u003C\u002Ftd>\u003Ctd>每条命令只存增量，轻\u003C\u002Ftd>\u003Ctd>每份快照存全量，重\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>前提\u003C\u002Ftd>\u003Ctd>操作必须可逆\u003C\u002Ftd>\u003Ctd>无要求，什么状态都能存\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>粒度\u003C\u002Ftd>\u003Ctd>单步撤销，天生精细\u003C\u002Ftd>\u003Ctd>一次跳一个存档点\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>高频操作\u003C\u002Ftd>\u003Ctd>优秀（Ctrl+Z 随便按）\u003C\u002Ftd>\u003Ctd>内存会爆炸\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n有个旧账顺便翻了：⑱ 的反面教材 BadEditor 用\"每步存全文快照\"实现撤销，被批得体无完肤——**它错不在快照思路，错在场景错配**：文本编辑是\"高频操作 + 可逆动作\"，正该走命令的增量路线；而本篇游戏存档是\"低频打点 + 不可逆状态\"，正该走备忘录的全量路线。同一味药，用对场景是仙丹，用错场景是毒药。\n\n实战里两者常常并肩：游戏每进一个存档点打一份备忘录（大颗粒），存档点内的每步操作记命令（细颗粒）——读档回大点，小撤销用命令，各司其职。\n\n## 写在最后\n\n备忘录模式一句话总结：**角色亲手打包黑盒快照，管理者只管保管搬运；封装滴水不漏，后悔药随取随用。**\n\n下一篇 ㉒ 中介者模式——角色存完档，去聊天室放松一下。聊天室里有个老问题：用户 A 想发消息给 B 和 C，B 想回 A，C 想私聊 A……每个用户都得直接持有其他所有用户的引用，N 个人就是 N×(N-1) 条关系线，来一个新人，全员改代码。**把网状依赖收拢成星型——所有人只认识群主一个**——中介者模式，下一篇见。\n","\u003Cblockquote>\n \u003Cp>行为型第八站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>⑱ 命令模式讲撤销时我们走了\"逆操作\"路线：撤销插入就是删除、撤销删除就是插回。轻巧，但有个前提——\u003Cstrong>每个操作都必须可逆\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>游戏角色可没有\"逆操作\"：血量、蓝量、金币、装备、坐标……打 Boss 团灭了，想把角色恢复到进本之前，怎么办？难道把所有字段 getter\u002Fsetter 全开放，让存档代码自己抄一份？——那封装就死透了，而且外挂狂喜。\u003C\u002Fp>\n\u003Cp>备忘录模式就是为这个困境而生的：\u003Cstrong>不破坏封装的前提下，把内部状态存下来、再原样恢复。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>一、备忘录模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. （在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便以后需要时将该对象恢复到原先保存的状态。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>定义第一句就是\"不破坏封装\"——这是备忘录模式和\"暴露字段随便抄\"的分界线，也是它全部技巧所在。\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Originator（原发器）\u003C\u002Ftd>\n   \u003Ctd>状态的主人：打包快照、从快照恢复\u003C\u002Ftd>\n   \u003Ctd>GameRole\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Memento（备忘录）\u003C\u002Ftd>\n   \u003Ctd>状态快照的容器：对外是个黑盒\u003C\u002Ftd>\n   \u003Ctd>RoleMemento\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Caretaker（管理者）\u003C\u002Ftd>\n   \u003Ctd>保管备忘录：只存不读、只搬运不拆箱\u003C\u002Ftd>\n   \u003Ctd>RoleStateCaretaker\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>这个模式解的是一个\"既要又要\"的矛盾：\u003Cstrong>存档必须读取角色的状态（不然存什么），恢复必须写回角色的状态（不然怎么还原）——但一旦把读写开放出去，封装就没了。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>解法是釜底抽薪：**打包和恢复这两个动作，都由角色亲自动手。**外界全程只搬运一个黑盒，从头到尾没碰到任何字段。类比游戏存档：存档时是你自己（角色）把状态写进存档卡，读档时也是你自己照着卡恢复——仓库管理员只负责把卡收好，他根本看不懂卡上写的是什么（也不想看懂）。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>封装完好无损\u003C\u002Fstrong>。外界拿不到字段，连存档器自己都读不懂快照内容——角色加十个字段，存档器一行代码不用改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>状态恢复的完整性有保证\u003C\u002Fstrong>。快照是角色亲手打包的，里面装了什么、怎么还原，只有角色知道，不会出现\"漏抄一个字段\"的残废存档。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>职责干净\u003C\u002Fstrong>。角色管打包还原，管理器管保管调度，各自单一职责。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>快照可复用\u003C\u002Fstrong>。存档槽可以做成列表——多存档位、自动存档、时间机器，都是管理者层面的小扩展。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>内存开销\u003C\u002Fstrong>。每份快照都是一份状态的完整拷贝，状态一大多存几份，内存就顶不住（⑱ 反面教材 BadEditor 的快照栈就是教训——这是\"高频快照 + 大状态\"的组合陷阱）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>黑盒的代价\u003C\u002Fstrong>：管理者想展示\"存档详情\"给用户看？做不到，内容被锁死了。需要展示就得额外开口子，封装的口子一开就收不回。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>类数量增加\u003C\u002Fstrong>。每个需要存档的原发器都得配一个备忘录类。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>只适合\"全量快照\"语义\u003C\u002Fstrong>：想只撤销\"半步\"（只回滚血量不回滚金币），备忘录原生不支持——粒度问题要靠拆多个备忘录类解决。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>需要把对象状态恢复到过去某个时点，且对象状态复杂\u002F含私有细节，不能或不该用\"逆操作\"来表达。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>游戏存档\u003C\u002Fstrong>：角色状态、地图进度、任务进度——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>事务保存点\u003C\u002Fstrong>：数据库 \u003Ccode>SAVEPOINT\u003C\u002Fcode> \u002F \u003Ccode>ROLLBACK TO\u003C\u002Fcode>，长事务打点回滚\u003C\u002Fli>\n \u003Cli>\u003Cstrong>编辑器\u002FIDE 状态恢复\u003C\u002Fstrong>：窗口布局、断点集、打开的文件列表\u003C\u002Fli>\n \u003Cli>\u003Cstrong>版本控制\u003C\u002Fstrong>：每次 commit 都是一次备忘录\u003C\u002Fli>\n \u003Cli>\u003Cstrong>草稿箱\u002F定时快照\u003C\u002Fstrong>：表单自动保存、图文编辑器的历史版本\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：操作可逆且高频（文本编辑的打字删除）——用 ⑱ 命令模式的增量撤销更轻；只是想跨进程传输对象——那是序列化，不需要\"黑盒\"；状态就两三个字段——直接 copy 个 record 就完事了。\u003C\u002Fp>\n\u003Ch2>四、实现方式：游戏角色的存档与读档\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：字段裸奔\u003C\u002Fh3>\n\u003Cp>先看最直觉的存档做法——把字段全开放出去，让存档代码自己抄：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadGameRole {\n\n    private int hp = 100;\n    private int mp = 80;\n    private int gold = 500;\n\n    public void fightBoss() {\n        hp = 10;\n        mp = 20;\n        gold = 1200;\n    }\n\n    public void display(String tag) {\n        System.out.println(\"  [角色] \" + tag + \"：HP=\" + hp + \", MP=\" + mp + \", 金币=\" + gold);\n    }\n\n    \u002F\u002F 为了让外界能存档\u002F读档，被迫开放所有字段的读写\n    \u002F\u002F 代价一：存档代码必须认识每个字段，角色以后加字段它就漏抄\n    \u002F\u002F 代价二：任何代码都能 setHp(99999)——外挂就是这么诞生的\n    public int getHp() { return hp; }\n    public void setHp(int hp) { this.hp = hp; }\n    public int getMp() { return mp; }\n    public void setMp(int mp) { this.mp = mp; }\n    public int getGold() { return gold; }\n    public void setGold(int gold) { this.gold = gold; }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>存档方\"人肉抄写\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">int savedHp = bad.getHp();\nint savedMp = bad.getMp();\nint savedGold = bad.getGold();\n\nbad.fightBoss();\n\nbad.setHp(savedHp);\nbad.setMp(savedMp);\nbad.setGold(savedGold);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>功能能用，三宗罪却在输出里现了形：存档代码必须逐字段认识角色（角色加个\"装备列表\"，这里必漏）；恢复靠人肉抄写（抄错一个字段，存档就是残废）；setter 全公开——\u003Ccode>setHp(99999)\u003C\u002Fcode> 一行，外挂诞生。\u003C\u002Fp>\n\u003Ch3>4.2 正解：角色亲手打包，管理者只管搬运\u003C\u002Fh3>\n\u003Cp>第一步，原发器 + 备忘录。关键设计：\u003Cstrong>RoleMemento 做成 GameRole 的静态内部类，字段和构造全是 private\u003C\u002Fstrong>——嵌套类之间可以互访私有成员，但外部类（包括管理者和任何代码）统统摸不到：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 原发器：拥有需要保存的状态，自己负责\"打包快照\"和\"从快照恢复\"\npublic class GameRole {\n\n    private int hp = 100;\n    private int mp = 80;\n    private int gold = 500;\n\n    public void fightBoss() {\n        hp = 10;      \u002F\u002F 打得很惨\n        mp = 20;\n        gold = 1200;  \u002F\u002F 但捡了点钱\n    }\n\n    \u002F\u002F 打包当前状态：只有自己能创建和读取备忘录\n    public RoleMemento createMemento() {\n        return new RoleMemento(hp, mp, gold);\n    }\n\n    \u002F\u002F 从快照恢复：只有自己读得到备忘录里的字段\n    public void restoreMemento(RoleMemento memento) {\n        this.hp = memento.hp;\n        this.mp = memento.mp;\n        this.gold = memento.gold;\n    }\n\n    public void display(String tag) {\n        System.out.println(\"  [角色] \" + tag + \"：HP=\" + hp + \", MP=\" + mp + \", 金币=\" + gold);\n    }\n\n    \u002F\u002F 备忘录：静态内部类，字段 private、构造 private\n    \u002F\u002F 管理者和外界拿在手里也只是个黑盒——看得见对象，摸不到内容\n    public static class RoleMemento {\n\n        private final int hp;\n        private final int mp;\n        private final int gold;\n\n        private RoleMemento(int hp, int mp, int gold) {\n            this.hp = hp;\n            this.mp = mp;\n            this.gold = gold;\n        }\n\n        @Override\n        public String toString() {\n            return \"快照 [HP=\" + hp + \", MP=\" + mp + \", 金币=\" + gold + \"]\";\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，管理者。它干的就是存和还，内容与它无关：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 管理者：只负责保管和交还备忘录，不看内容、不乱动\n\u002F\u002F 它持有 RoleMemento 引用，但字段和构造全是 private——想读想改都没门\npublic class RoleStateCaretaker {\n\n    private GameRole.RoleMemento memento;\n\n    public void saveMemento(GameRole.RoleMemento memento) {\n        this.memento = memento;\n        System.out.println(\"  [管理器] 存档完成：\" + memento);\n    }\n\n    public GameRole.RoleMemento retrieveMemento() {\n        System.out.println(\"  [管理器] 交还存档：\" + memento);\n        return memento;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，测试——存档、打 Boss、团灭、读档：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class MementoTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：字段裸奔 ==========\");\n        BadGameRole bad = new BadGameRole();\n        bad.display(\"战斗前\");\n\n        int savedHp = bad.getHp();\n        int savedMp = bad.getMp();\n        int savedGold = bad.getGold();\n\n        bad.fightBoss();\n        bad.display(\"战斗后\");\n\n        bad.setHp(savedHp);\n        bad.setMp(savedMp);\n        bad.setGold(savedGold);\n        bad.display(\"人肉读档\");\n\n        bad.setHp(99999);\n        bad.display(\"外挂之后\");\n\n        System.out.println();\n        System.out.println(\"========== 正解：备忘录模式 ==========\");\n        GameRole role = new GameRole();\n        RoleStateCaretaker caretaker = new RoleStateCaretaker();\n\n        role.display(\"战斗前\");\n        caretaker.saveMemento(role.createMemento());\n\n        role.fightBoss();\n        role.display(\"战斗后\");\n\n        System.out.println(\"  [剧情] 团灭了，读档重打——\");\n        role.restoreMemento(caretaker.retrieveMemento());\n        role.display(\"读档后\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">========== 反面教材：字段裸奔 ==========\n  [角色] 战斗前：HP=100, MP=80, 金币=500\n  [角色] 战斗后：HP=10, MP=20, 金币=1200\n  [角色] 人肉读档：HP=100, MP=80, 金币=500\n  [角色] 外挂之后：HP=99999, MP=80, 金币=500\n\n========== 正解：备忘录模式 ==========\n  [角色] 战斗前：HP=100, MP=80, 金币=500\n  [管理器] 存档完成：快照 [HP=100, MP=80, 金币=500]\n  [角色] 战斗后：HP=10, MP=20, 金币=1200\n  [剧情] 团灭了，读档重打——\n  [管理器] 交还存档：快照 [HP=100, MP=80, 金币=500]\n  [角色] 读档后：HP=100, MP=80, 金币=500\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>**细节一：打包和恢复都是角色亲手做的。**createMemento 里读字段、restoreMemento 里写字段的代码都在 GameRole 内部——外界从头到尾没接触过任何字段。所以角色以后加\"装备列表\"\"坐标\"\"Buff 集合\"，只需要改这两个方法，管理者和所有调用方零改动。对比反面教材\"加字段 → 全世界的存档代码跟着改\"，这就是\"封装守住了\"的实际含义。\u003C\u002Fp>\n\u003Cp>**细节二：静态内部类 + private，是\"宽窄接口\"的 Java 惯用实现。**备忘录模式的理论里有个双接口概念：Originator 拿宽接口（读写全部内容），Caretaker 拿窄接口（只能持有引用）。Java 里不需要真写两个接口——嵌套类天然互访私有成员（编译器会合成桥方法），而外部任何类都摸不到 private。于是同一个 RoleMemento，在 GameRole 眼里是透明的，在 Caretaker 眼里是黑盒。\"看得见摸不着\"，编译器站岗。\u003C\u002Fp>\n\u003Cp>**细节三：管理者为什么必须\"不读内容\"？**不是技术不能，是制度不许。Caretaker 的职责是\"什么时候存、存多久、存哪个槽位\"——一旦它开始读快照内容做展示、做判断，它就懂了业务，黑盒就废了：角色改字段，管理器也要跟着改。窄接口不是能力限制，是防腐层。真要展示存档信息？让 GameRole 提供 \u003Ccode>describe(Memento)\u003C\u002Fcode> 之类的受控方法，绕回角色自己身上。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Cp>**Git——世界上最大的备忘录系统。**每次 commit 是一次 createMemento（把工作区状态打包成不可变的快照对象），\u003Ccode>.git\u003C\u002Fcode> 仓库是 Caretaker（只负责存和取，快照内容按内容寻址、谁也别想手改），\u003Ccode>git restore\u003C\u002Fcode> \u002F \u003Ccode>git checkout\u003C\u002Fcode> 是 restoreMemento。\"写代码前先 commit 一版\"和\"打 Boss 前先存个档\"，是同一个求生智慧。\u003C\u002Fp>\n\u003Cp>**JDBC Savepoint——数据库事务的存档点。**长事务里 \u003Ccode>Connection.setSavepoint(\"sp1\")\u003C\u002Fcode> 打一个存档点，后面操作出错 \u003Ccode>conn.rollback(sp1)\u003C\u002Fcode> 只回滚到打点处、不毁掉之前的成果——正是\"多存档位 + 定点读档\"的工业级实现。打点就是快照，回滚就是恢复。\u003C\u002Fp>\n\u003Cp>**IDE 本地历史。**IntelliJ 的 Local History、VS Code 的 Timeline：你每次保存、甚至每次失去焦点，IDE 都悄悄存一份文件快照——误删了代码、refactor 翻车了，右键 Local History 一秒回到五分钟前。这类功能敢做成\"全自动\"，底气就是快照的打包\u002F恢复全在 IDE 内部闭环，外界（包括你）只负责挑时间点。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>浏览器后退栈、操作系统的系统还原点、手机云备份\u003C\u002Fstrong>——所有\"回到过去\"的按钮，背后都是同一个模式。\u003C\u002Fp>\n\u003Cp>一句话总结这一节：\u003Cstrong>凡是\"把现在存起来，以便将来后悔\"的地方，备忘录模式都在默默服务。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>六、撤销问题的完整拼图：命令 + 备忘录\u003C\u002Fh2>\n\u003Cp>至此，\"后悔药\"的两种配方集齐了，合上账本：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>命令模式 ⑱\u003C\u002Fth>\n   \u003Cth>备忘录模式 ㉑\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>撤销原理\u003C\u002Ftd>\n   \u003Ctd>逆操作回放（撤销插入=删除）\u003C\u002Ftd>\n   \u003Ctd>快照回滚（整个状态倒回）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>存储\u003C\u002Ftd>\n   \u003Ctd>每条命令只存增量，轻\u003C\u002Ftd>\n   \u003Ctd>每份快照存全量，重\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>前提\u003C\u002Ftd>\n   \u003Ctd>操作必须可逆\u003C\u002Ftd>\n   \u003Ctd>无要求，什么状态都能存\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>粒度\u003C\u002Ftd>\n   \u003Ctd>单步撤销，天生精细\u003C\u002Ftd>\n   \u003Ctd>一次跳一个存档点\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>高频操作\u003C\u002Ftd>\n   \u003Ctd>优秀（Ctrl+Z 随便按）\u003C\u002Ftd>\n   \u003Ctd>内存会爆炸\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>有个旧账顺便翻了：⑱ 的反面教材 BadEditor 用\"每步存全文快照\"实现撤销，被批得体无完肤——\u003Cstrong>它错不在快照思路，错在场景错配\u003C\u002Fstrong>：文本编辑是\"高频操作 + 可逆动作\"，正该走命令的增量路线；而本篇游戏存档是\"低频打点 + 不可逆状态\"，正该走备忘录的全量路线。同一味药，用对场景是仙丹，用错场景是毒药。\u003C\u002Fp>\n\u003Cp>实战里两者常常并肩：游戏每进一个存档点打一份备忘录（大颗粒），存档点内的每步操作记命令（细颗粒）——读档回大点，小撤销用命令，各司其职。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>备忘录模式一句话总结：\u003Cstrong>角色亲手打包黑盒快照，管理者只管保管搬运；封装滴水不漏，后悔药随取随用。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>下一篇 ㉒ 中介者模式——角色存完档，去聊天室放松一下。聊天室里有个老问题：用户 A 想发消息给 B 和 C，B 想回 A，C 想私聊 A……每个用户都得直接持有其他所有用户的引用，N 个人就是 N×(N-1) 条关系线，来一个新人，全员改代码。\u003Cstrong>把网状依赖收拢成星型——所有人只认识群主一个\u003C\u002Fstrong>——中介者模式，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,62],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113932265082851329","design-pattern-18","设计模式修炼之路 ⑱ 命令模式——把请求封装成对象","把请求封装成对象、增量式撤销、白送的排队与重放、Executor 的铁证",2089,7080,18,"2024-08-09T12:36:13",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":36,"publishedAt":61},"2113933842262790145","design-pattern-19","设计模式修炼之路 ⑲ 状态模式——状态驱动行为","状态驱动行为、流转规则住进状态家、Thread 状态机、状态 vs 策略终局对决",1320,6715,"2024-09-02T16:09:32",{"id":63,"slug":64,"title":65,"summary":66,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":67,"likeCount":4,"wordCount":68,"readingMinutes":36,"publishedAt":69},"2113934247046680578","design-pattern-22","设计模式修炼之路 ㉒ 中介者模式——网状依赖变星型","网状依赖变星型、N×(N-1) 收成 N 条、聊天室聚餐事故、中介者 vs 观察者",1550,6591,"2024-11-01T18:45:07",{"code":4,"message":5,"data":71},{"prev":72,"next":80},{"id":73,"slug":74,"title":75,"summary":76,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":77,"likeCount":4,"wordCount":78,"readingMinutes":36,"publishedAt":79},"2113933908541181954","design-pattern-20","设计模式修炼之路 ⑳ 迭代器模式——统一遍历协议","统一遍历协议、for-each 只是语法糖、手写可迭代书架、fail-fast 的哲学",2334,6515,"2024-09-17T21:25:50",{"id":63,"slug":64,"title":65,"summary":66,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":67,"likeCount":4,"wordCount":68,"readingMinutes":36,"publishedAt":69},{"code":4,"message":5,"data":82},[83,89,94,100],{"id":84,"name":85,"slug":86,"description":87,"sortOrder":4,"articleCount":88},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":90,"slug":91,"description":92,"sortOrder":4,"articleCount":93},"设计模式","design-pattern","设计模式系列","34",{"id":95,"name":96,"slug":97,"description":98,"sortOrder":33,"articleCount":99},"1946000000000000002","随笔","essay","生活与思考","0",{"id":101,"name":102,"slug":103,"description":104,"sortOrder":105,"articleCount":106},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":108,"toc":109},"\u003Cblockquote>\n \u003Cp>行为型第八站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>⑱ 命令模式讲撤销时我们走了\"逆操作\"路线：撤销插入就是删除、撤销删除就是插回。轻巧，但有个前提——\u003Cstrong>每个操作都必须可逆\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>游戏角色可没有\"逆操作\"：血量、蓝量、金币、装备、坐标……打 Boss 团灭了，想把角色恢复到进本之前，怎么办？难道把所有字段 getter\u002Fsetter 全开放，让存档代码自己抄一份？——那封装就死透了，而且外挂狂喜。\u003C\u002Fp>\n\u003Cp>备忘录模式就是为这个困境而生的：\u003Cstrong>不破坏封装的前提下，把内部状态存下来、再原样恢复。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、备忘录模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. （在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便以后需要时将该对象恢复到原先保存的状态。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>定义第一句就是\"不破坏封装\"——这是备忘录模式和\"暴露字段随便抄\"的分界线，也是它全部技巧所在。\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Originator（原发器）\u003C\u002Ftd>\n   \u003Ctd>状态的主人：打包快照、从快照恢复\u003C\u002Ftd>\n   \u003Ctd>GameRole\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Memento（备忘录）\u003C\u002Ftd>\n   \u003Ctd>状态快照的容器：对外是个黑盒\u003C\u002Ftd>\n   \u003Ctd>RoleMemento\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Caretaker（管理者）\u003C\u002Ftd>\n   \u003Ctd>保管备忘录：只存不读、只搬运不拆箱\u003C\u002Ftd>\n   \u003Ctd>RoleStateCaretaker\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>这个模式解的是一个\"既要又要\"的矛盾：\u003Cstrong>存档必须读取角色的状态（不然存什么），恢复必须写回角色的状态（不然怎么还原）——但一旦把读写开放出去，封装就没了。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>解法是釜底抽薪：**打包和恢复这两个动作，都由角色亲自动手。**外界全程只搬运一个黑盒，从头到尾没碰到任何字段。类比游戏存档：存档时是你自己（角色）把状态写进存档卡，读档时也是你自己照着卡恢复——仓库管理员只负责把卡收好，他根本看不懂卡上写的是什么（也不想看懂）。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>封装完好无损\u003C\u002Fstrong>。外界拿不到字段，连存档器自己都读不懂快照内容——角色加十个字段，存档器一行代码不用改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>状态恢复的完整性有保证\u003C\u002Fstrong>。快照是角色亲手打包的，里面装了什么、怎么还原，只有角色知道，不会出现\"漏抄一个字段\"的残废存档。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>职责干净\u003C\u002Fstrong>。角色管打包还原，管理器管保管调度，各自单一职责。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>快照可复用\u003C\u002Fstrong>。存档槽可以做成列表——多存档位、自动存档、时间机器，都是管理者层面的小扩展。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>内存开销\u003C\u002Fstrong>。每份快照都是一份状态的完整拷贝，状态一大多存几份，内存就顶不住（⑱ 反面教材 BadEditor 的快照栈就是教训——这是\"高频快照 + 大状态\"的组合陷阱）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>黑盒的代价\u003C\u002Fstrong>：管理者想展示\"存档详情\"给用户看？做不到，内容被锁死了。需要展示就得额外开口子，封装的口子一开就收不回。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>类数量增加\u003C\u002Fstrong>。每个需要存档的原发器都得配一个备忘录类。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>只适合\"全量快照\"语义\u003C\u002Fstrong>：想只撤销\"半步\"（只回滚血量不回滚金币），备忘录原生不支持——粒度问题要靠拆多个备忘录类解决。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>需要把对象状态恢复到过去某个时点，且对象状态复杂\u002F含私有细节，不能或不该用\"逆操作\"来表达。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>游戏存档\u003C\u002Fstrong>：角色状态、地图进度、任务进度——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>事务保存点\u003C\u002Fstrong>：数据库 \u003Ccode>SAVEPOINT\u003C\u002Fcode> \u002F \u003Ccode>ROLLBACK TO\u003C\u002Fcode>，长事务打点回滚\u003C\u002Fli>\n \u003Cli>\u003Cstrong>编辑器\u002FIDE 状态恢复\u003C\u002Fstrong>：窗口布局、断点集、打开的文件列表\u003C\u002Fli>\n \u003Cli>\u003Cstrong>版本控制\u003C\u002Fstrong>：每次 commit 都是一次备忘录\u003C\u002Fli>\n \u003Cli>\u003Cstrong>草稿箱\u002F定时快照\u003C\u002Fstrong>：表单自动保存、图文编辑器的历史版本\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：操作可逆且高频（文本编辑的打字删除）——用 ⑱ 命令模式的增量撤销更轻；只是想跨进程传输对象——那是序列化，不需要\"黑盒\"；状态就两三个字段——直接 copy 个 record 就完事了。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：游戏角色的存档与读档\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：字段裸奔\u003C\u002Fh3>\n\u003Cp>先看最直觉的存档做法——把字段全开放出去，让存档代码自己抄：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadGameRole\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 100\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 80\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 500\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> fightBoss\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 20\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1200\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">tag\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [角色] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> tag \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"：HP=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", MP=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 金币=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 为了让外界能存档\u002F读档，被迫开放所有字段的读写\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 代价一：存档代码必须认识每个字段，角色以后加字段它就漏抄\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 代价二：任何代码都能 setHp(99999)——外挂就是这么诞生的\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> getHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> setHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> hp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> getMp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> setMp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> mp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> getGold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> setGold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> gold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>存档方\"人肉抄写\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> savedHp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> savedMp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getMp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> savedGold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getGold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">fightBoss\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(savedHp);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setMp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(savedMp);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setGold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(savedGold);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>功能能用，三宗罪却在输出里现了形：存档代码必须逐字段认识角色（角色加个\"装备列表\"，这里必漏）；恢复靠人肉抄写（抄错一个字段，存档就是残废）；setter 全公开——\u003Ccode>setHp(99999)\u003C\u002Fcode> 一行，外挂诞生。\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：角色亲手打包，管理者只管搬运\u003C\u002Fh3>\n\u003Cp>第一步，原发器 + 备忘录。关键设计：\u003Cstrong>RoleMemento 做成 GameRole 的静态内部类，字段和构造全是 private\u003C\u002Fstrong>——嵌套类之间可以互访私有成员，但外部类（包括管理者和任何代码）统统摸不到：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 原发器：拥有需要保存的状态，自己负责\"打包快照\"和\"从快照恢复\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> GameRole\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 100\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 80\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 500\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> fightBoss\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;      \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 打得很惨\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 20\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1200\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 但捡了点钱\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 打包当前状态：只有自己能创建和读取备忘录\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> RoleMemento \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">createMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RoleMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(hp, mp, gold);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 从快照恢复：只有自己读得到备忘录里的字段\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> restoreMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RoleMemento \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">memento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento.hp;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento.mp;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento.gold;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">tag\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [角色] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> tag \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"：HP=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", MP=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 金币=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 备忘录：静态内部类，字段 private、构造 private\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 管理者和外界拿在手里也只是个黑盒——看得见对象，摸不到内容\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RoleMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RoleMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> hp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> mp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> gold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"快照 [HP=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> hp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", MP=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 金币=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> gold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"]\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，管理者。它干的就是存和还，内容与它无关：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 管理者：只负责保管和交还备忘录，不看内容、不乱动\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 它持有 RoleMemento 引用，但字段和构造全是 private——想读想改都没门\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RoleStateCaretaker\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> GameRole.RoleMemento memento;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> saveMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(GameRole.RoleMemento \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">memento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.memento \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [管理器] 存档完成：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> GameRole.RoleMemento \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">retrieveMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [管理器] 交还存档：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memento;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，测试——存档、打 Boss、团灭、读档：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MementoTest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 反面教材：字段裸奔 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        BadGameRole bad \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadGameRole\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"战斗前\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> savedHp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> savedMp \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getMp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> savedGold \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getGold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">fightBoss\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"战斗后\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(savedHp);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setMp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(savedMp);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setGold\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(savedGold);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"人肉读档\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setHp\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">99999\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"外挂之后\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 正解：备忘录模式 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        GameRole role \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> GameRole\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        RoleStateCaretaker caretaker \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RoleStateCaretaker\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        role.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"战斗前\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        caretaker.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">saveMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(role.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">createMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        role.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">fightBoss\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        role.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"战斗后\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [剧情] 团灭了，读档重打——\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        role.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">restoreMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(caretaker.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">retrieveMemento\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        role.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">display\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"读档后\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：字段裸奔 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 战斗前：HP=100, MP=80, 金币=500\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 战斗后：HP=10, MP=20, 金币=1200\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 人肉读档：HP=100, MP=80, 金币=500\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 外挂之后：HP=99999, MP=80, 金币=500\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>========== 正解：备忘录模式 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 战斗前：HP=100, MP=80, 金币=500\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [管理器] 存档完成：快照 [HP=100, MP=80, 金币=500]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 战斗后：HP=10, MP=20, 金币=1200\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [剧情] 团灭了，读档重打——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [管理器] 交还存档：快照 [HP=100, MP=80, 金币=500]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [角色] 读档后：HP=100, MP=80, 金币=500\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"toc-7\">4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>**细节一：打包和恢复都是角色亲手做的。**createMemento 里读字段、restoreMemento 里写字段的代码都在 GameRole 内部——外界从头到尾没接触过任何字段。所以角色以后加\"装备列表\"\"坐标\"\"Buff 集合\"，只需要改这两个方法，管理者和所有调用方零改动。对比反面教材\"加字段 → 全世界的存档代码跟着改\"，这就是\"封装守住了\"的实际含义。\u003C\u002Fp>\n\u003Cp>**细节二：静态内部类 + private，是\"宽窄接口\"的 Java 惯用实现。**备忘录模式的理论里有个双接口概念：Originator 拿宽接口（读写全部内容），Caretaker 拿窄接口（只能持有引用）。Java 里不需要真写两个接口——嵌套类天然互访私有成员（编译器会合成桥方法），而外部任何类都摸不到 private。于是同一个 RoleMemento，在 GameRole 眼里是透明的，在 Caretaker 眼里是黑盒。\"看得见摸不着\"，编译器站岗。\u003C\u002Fp>\n\u003Cp>**细节三：管理者为什么必须\"不读内容\"？**不是技术不能，是制度不许。Caretaker 的职责是\"什么时候存、存多久、存哪个槽位\"——一旦它开始读快照内容做展示、做判断，它就懂了业务，黑盒就废了：角色改字段，管理器也要跟着改。窄接口不是能力限制，是防腐层。真要展示存档信息？让 GameRole 提供 \u003Ccode>describe(Memento)\u003C\u002Fcode> 之类的受控方法，绕回角色自己身上。\u003C\u002Fp>\n\u003Ch2 id=\"toc-8\">五、在框架里找它\u003C\u002Fh2>\n\u003Cp>**Git——世界上最大的备忘录系统。**每次 commit 是一次 createMemento（把工作区状态打包成不可变的快照对象），\u003Ccode>.git\u003C\u002Fcode> 仓库是 Caretaker（只负责存和取，快照内容按内容寻址、谁也别想手改），\u003Ccode>git restore\u003C\u002Fcode> \u002F \u003Ccode>git checkout\u003C\u002Fcode> 是 restoreMemento。\"写代码前先 commit 一版\"和\"打 Boss 前先存个档\"，是同一个求生智慧。\u003C\u002Fp>\n\u003Cp>**JDBC Savepoint——数据库事务的存档点。**长事务里 \u003Ccode>Connection.setSavepoint(\"sp1\")\u003C\u002Fcode> 打一个存档点，后面操作出错 \u003Ccode>conn.rollback(sp1)\u003C\u002Fcode> 只回滚到打点处、不毁掉之前的成果——正是\"多存档位 + 定点读档\"的工业级实现。打点就是快照，回滚就是恢复。\u003C\u002Fp>\n\u003Cp>**IDE 本地历史。**IntelliJ 的 Local History、VS Code 的 Timeline：你每次保存、甚至每次失去焦点，IDE 都悄悄存一份文件快照——误删了代码、refactor 翻车了，右键 Local History 一秒回到五分钟前。这类功能敢做成\"全自动\"，底气就是快照的打包\u002F恢复全在 IDE 内部闭环，外界（包括你）只负责挑时间点。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>浏览器后退栈、操作系统的系统还原点、手机云备份\u003C\u002Fstrong>——所有\"回到过去\"的按钮，背后都是同一个模式。\u003C\u002Fp>\n\u003Cp>一句话总结这一节：\u003Cstrong>凡是\"把现在存起来，以便将来后悔\"的地方，备忘录模式都在默默服务。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2 id=\"toc-9\">六、撤销问题的完整拼图：命令 + 备忘录\u003C\u002Fh2>\n\u003Cp>至此，\"后悔药\"的两种配方集齐了，合上账本：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>命令模式 ⑱\u003C\u002Fth>\n   \u003Cth>备忘录模式 ㉑\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>撤销原理\u003C\u002Ftd>\n   \u003Ctd>逆操作回放（撤销插入=删除）\u003C\u002Ftd>\n   \u003Ctd>快照回滚（整个状态倒回）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>存储\u003C\u002Ftd>\n   \u003Ctd>每条命令只存增量，轻\u003C\u002Ftd>\n   \u003Ctd>每份快照存全量，重\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>前提\u003C\u002Ftd>\n   \u003Ctd>操作必须可逆\u003C\u002Ftd>\n   \u003Ctd>无要求，什么状态都能存\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>粒度\u003C\u002Ftd>\n   \u003Ctd>单步撤销，天生精细\u003C\u002Ftd>\n   \u003Ctd>一次跳一个存档点\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>高频操作\u003C\u002Ftd>\n   \u003Ctd>优秀（Ctrl+Z 随便按）\u003C\u002Ftd>\n   \u003Ctd>内存会爆炸\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>有个旧账顺便翻了：⑱ 的反面教材 BadEditor 用\"每步存全文快照\"实现撤销，被批得体无完肤——\u003Cstrong>它错不在快照思路，错在场景错配\u003C\u002Fstrong>：文本编辑是\"高频操作 + 可逆动作\"，正该走命令的增量路线；而本篇游戏存档是\"低频打点 + 不可逆状态\"，正该走备忘录的全量路线。同一味药，用对场景是仙丹，用错场景是毒药。\u003C\u002Fp>\n\u003Cp>实战里两者常常并肩：游戏每进一个存档点打一份备忘录（大颗粒），存档点内的每步操作记命令（细颗粒）——读档回大点，小撤销用命令，各司其职。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cp>备忘录模式一句话总结：\u003Cstrong>角色亲手打包黑盒快照，管理者只管保管搬运；封装滴水不漏，后悔药随取随用。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>下一篇 ㉒ 中介者模式——角色存完档，去聊天室放松一下。聊天室里有个老问题：用户 A 想发消息给 B 和 C，B 想回 A，C 想私聊 A……每个用户都得直接持有其他所有用户的引用，N 个人就是 N×(N-1) 条关系线，来一个新人，全员改代码。\u003Cstrong>把网状依赖收拢成星型——所有人只认识群主一个\u003C\u002Fstrong>——中介者模式，下一篇见。\u003C\u002Fp>",[110,113,116,119,122,126,129,132,135,138],{"id":111,"text":112,"level":105},"toc-1","一、备忘录模式是什么",{"id":114,"text":115,"level":105},"toc-2","二、优点与缺点",{"id":117,"text":118,"level":105},"toc-3","三、什么时候用",{"id":120,"text":121,"level":105},"toc-4","四、实现方式：游戏角色的存档与读档",{"id":123,"text":124,"level":125},"toc-5","4.1 反面教材：字段裸奔",3,{"id":127,"text":128,"level":125},"toc-6","4.2 正解：角色亲手打包，管理者只管搬运",{"id":130,"text":131,"level":125},"toc-7","4.3 三处细节咂摸一下",{"id":133,"text":134,"level":105},"toc-8","五、在框架里找它",{"id":136,"text":137,"level":105},"toc-9","六、撤销问题的完整拼图：命令 + 备忘录",{"id":139,"text":140,"level":105},"toc-10","写在最后"]