16.C++设计模式-备忘录模式

第一部分:模式基础与实现

1. 模式定义

备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便在将来某个时刻可以将该对象恢复到原先保存的状态。

2. 核心角色

创建/恢复

存储/管理

Originator

-state: string

+setMemento(Memento m)

+createMemento() : Memento

Memento

-state: string

+getState() : string

Caretaker

-mementos: list

+addMemento(Memento m)

+getMemento(index) : Memento

角色说明:

  • Originator(发起人):需要保存状态的对象
  • Memento(备忘录):存储Originator内部状态的对象
  • Caretaker(管理者):负责保存和管理备忘录

3. 应用场景

场景说明典型案例
撤销操作需要回滚到之前状态Ctrl+Z、编辑器撤销
保存快照定期保存状态用于恢复游戏存档、虚拟机快照
事务回滚数据库或业务操作失败回滚银行转账、订单操作
历史记录记录操作历史用于回溯浏览器后退按钮

4. 完整C++代码示例

#include <iostream>
#include <string>
#include <vector>
#include <memory>

// 备忘录类 - 存储状态
class Memento {
private:
    std::string state;
    int version;
    
public:
    Memento(const std::string& state, int version) 
        : state(state), version(version) {}
    
    std::string getState() const { return state; }
    int getVersion() const { return version; }
    
    // 仅允许Originator访问状态设置(友元类)
    friend class TextEditor;
};

// 发起人类 - 文本编辑器
class TextEditor {
private:
    std::string content;
    std::string cursorPosition;
    int fontSize;
    
public:
    TextEditor() : fontSize(12) {}
    
    void write(const std::string& text) {
        content += text;
    }
    
    void setCursor(const std::string& pos) {
        cursorPosition = pos;
    }
    
    void setFontSize(int size) {
        fontSize = size;
    }
    
    // 创建备忘录(保存快照)
    std::unique_ptr<Memento> createMemento() {
        std::string fullState = content + "|" + cursorPosition + "|" + std::to_string(fontSize);
        return std::make_unique<Memento>(fullState, getVersion());
    }
    
    // 从备忘录恢复
    void restoreFromMemento(const Memento& memento) {
        std::string state = memento.getState();
        size_t pos1 = state.find('|');
        size_t pos2 = state.find('|', pos1 + 1);
        
        content = state.substr(0, pos1);
        cursorPosition = state.substr(pos1 + 1, pos2 - pos1 - 1);
        fontSize = std::stoi(state.substr(pos2 + 1));
        
        std::cout << "恢复版本 " << memento.getVersion() << std::endl;
    }
    
    void display() {
        std::cout << "--- 编辑器状态 ---" << std::endl;
        std::cout << "内容: " << content << std::endl;
        std::cout << "光标: " << cursorPosition << std::endl;
        std::cout << "字号: " << fontSize << std::endl;
        std::cout << "----------------" << std::endl;
    }
    
private:
    int getVersion() const {
        static int counter = 0;
        return ++counter;
    }
};

// 管理人类 - 历史记录管理器
class HistoryManager {
private:
    std::vector<std::unique_ptr<Memento>> history;
    int currentIndex = -1;
    
public:
    void save(TextEditor& editor) {
        // 删除当前索引之后的所有记录(实现撤销后的新分支)
        history.resize(currentIndex + 1);
        
        history.push_back(editor.createMemento());
        currentIndex++;
        
        std::cout << "保存状态,当前版本: " << currentIndex + 1 << std::endl;
    }
    
    void undo(TextEditor& editor) {
        if (currentIndex > 0) {
            currentIndex--;
            editor.restoreFromMemento(*history[currentIndex]);
            std::cout << "撤销成功" << std::endl;
        } else {
            std::cout << "无法撤销:已经是最早版本" << std::endl;
        }
    }
    
    void redo(TextEditor& editor) {
        if (currentIndex + 1 < history.size()) {
            currentIndex++;
            editor.restoreFromMemento(*history[currentIndex]);
            std::cout << "重做成功" << std::endl;
        } else {
            std::cout << "无法重做:已经是最新版本" << std::endl;
        }
    }
    
    void showHistory() {
        std::cout << "\n历史版本记录:" << std::endl;
        for (size_t i = 0; i < history.size(); i++) {
            std::cout << "  版本 " << (i + 1) << ": " 
                     << history[i]->getState() << std::endl;
        }
    }
};

// 客户端代码
int main() {
    TextEditor editor;
    HistoryManager history;
    
    // 初始状态
    editor.write("Hello ");
    editor.setCursor("第1行第6列");
    editor.setFontSize(12);
    history.save(editor);
    
    // 修改1
    editor.write("World!");
    editor.setCursor("第1行第12列");
    history.save(editor);
    
    // 修改2
    editor.setFontSize(16);
    editor.write(" Welcome to Memento!");
    editor.setCursor("第1行第32列");
    history.save(editor);
    
    editor.display();
    
    // 撤销两次
    history.undo(editor);
    editor.display();
    
    history.undo(editor);
    editor.display();
    
    // 重做一次
    history.redo(editor);
    editor.display();
    
    history.showHistory();
    
    return 0;
}

5. 运行结果

保存状态,当前版本: 1
保存状态,当前版本: 2
保存状态,当前版本: 3
--- 编辑器状态 ---
内容: Hello World! Welcome to Memento!
光标: 第1行第32列
字号: 16
----------------
恢复版本 2
撤销成功
--- 编辑器状态 ---
内容: Hello World!
光标: 第1行第12列
字号: 12
----------------
恢复版本 1
撤销成功
--- 编辑器状态 ---
内容: Hello 
光标: 第1行第6列
字号: 12
----------------
恢复版本 2
重做成功
--- 编辑器状态 ---
内容: Hello World!
光标: 第1行第12列
字号: 12
----------------

历史版本记录:
  版本 1: Hello |第1行第6列|12
  版本 2: Hello World!|第1行第12列|12
  版本 3: Hello World! Welcome to Memento!|第1行第32列|16

6. 优缺点

优点:

  • ✅ 保持封装边界:不暴露Originator内部细节
  • ✅ 简化Originator:不需要管理状态历史
  • ✅ 灵活的状态管理:支持撤销/重做功能

缺点:

  • ❌ 内存消耗:大量备忘录可能占用较多内存
  • ❌ 性能开销:频繁保存状态影响性能
  • ❌ 管理复杂:需要实现完整的生命周期管理

7. 优化建议

// 优化1:限制历史记录数量
class LimitedHistoryManager {
    static constexpr size_t MAX_HISTORY = 50;
    std::vector<std::unique_ptr<Memento>> history;
};

// 优化2:支持增量保存
class DeltaMemento {
    std::vector<std::pair<std::string, std::string>> changes;
};

第二部分:核心思想与深度思维解析

一、核心思想:三个维度的哲学思考

备忘录模式
核心思想

时间维度的思考

状态的持久化保存

时间旅行能力

快照即时间切片

封装维度的思考

不破窗原则

最小知识原则

单一职责分离

代理维度的思考

状态外部化

访问权限控制

记忆与主体分离

二、深度思维解析

2.1 "记忆外置"思维

核心洞察: 将"记忆"从"主体"中分离出来,就像人类可以写下日记来记录状态。

// 反面教材:状态管理混杂在业务逻辑中
class BadEditor {
    std::vector<std::string> history;  // 状态和历史混杂
    std::string content;
    
    void badUndo() {
        // 业务逻辑中混杂状态管理
        content = history.back();  // 破坏了封装
    }
};

// 正面案例:记忆外置
class GoodEditor {
    std::string content;
    
    // 只负责自己的业务,将记忆交给专门的管理者
    Memento* save() { return new Memento(content); }
    void restore(Memento* m) { content = m->getContent(); }
};

class Memento {
    std::string snapshot;  // 纯粹的记忆载体
};

思维启示:

  • 不要试图让一个对象既处理业务又管理历史
  • 记忆是一种可以分离的独立实体
  • 将职责分解,每个部分只做一件事
2.2 "时间胶囊"思维

将对象的不同时刻状态封装成一个个"时间胶囊",可以随时打开任何一个。

T0初始状态胶囊0创建T1第一次修改胶囊1创建T2第二次修改胶囊2创建恢复操作选择T1状态打开胶囊1恢复到T1时刻时间胶囊概念
// 时间胶囊的实现思维
class TimeCapsule {
    struct Snapshot {
        std::chrono::system_clock::time_point timestamp;
        std::string state;
        std::string description;  // 为状态添加语义
    };
    
    std::vector<Snapshot> capsules;
    
public:
    // 就像把当前时刻装进瓶子
    void buryCapsule(const std::string& state, const std::string& desc) {
        capsules.push_back({std::chrono::system_clock::now(), state, desc});
    }
    
    // 打开某个时间点的瓶子
    std::string openCapsule(int index) {
        return capsules[index].state;
    }
};
2.3 "黑盒封装"思维

备忘录模式最精妙之处:让对象主动吐出状态,而不是被动被读取状态

正确思维:内部生成

请求保存

内部生成

只读访问

Client

Originator

Memento

Caretaker

错误思维:外部窥探

getState

暴露所有细节

Client

Originator

Memento

思维对比:

// ❌ 错误思维:外部窥探模式
class BrokenMementoPattern {
    class Originator {
    public:
        std::string state;  // 公开状态,破坏封装
    };
    
    class Caretaker {
        void save(Originator& o) {
            // 直接读取内部状态
            memento.state = o.state;  // 耦合太强
        }
    };
};

// ✅ 正确思维:自主生成模式
class CorrectMementoPattern {
    class Originator {
    private:
        std::string secret;  // 完全隐藏
        
    public:
        // 主动"吐出"备忘录
        Memento* createMemento() {
            return new Memento(secret);  // 自己打包状态
        }
        
        // 主动"吞入"备忘录
        void restore(Memento* m) {
            secret = m->getSecret();  // 自己解包
        }
    };
    
    class Memento {
        std::string secret;  // 只有Originator能访问
        friend class Originator;  // 关键:使用友元控制访问
    };
};

三、类比思维:现实世界的映射

现实世界编程世界思维要点
游戏存档Memento保存的是完整状态快照
保险柜备忘录对象保护内部信息不被乱改
历史博物馆Caretaker保管文物但不解读内容
时间机器恢复操作回到过去的某个状态
// 类比:游戏存档系统
class GamePlayer {
    int level;
    int health;
    std::vector<std::string> items;
    
    // 就像在游戏菜单中"保存"  
    SaveFile* quickSave() {
        return new SaveFile(level, health, items);
    }
    
    // 就像"读取"存档
    void quickLoad(SaveFile* save) {
        this->level = save->level;
        this->health = save->health;
        this->items = save->items;
    }
};

四、模式思维的演进路径

发现问题

方案1

缺点:职责过重

缺点:破坏封装

朴素思维

状态保存在哪里?

在对象内部保存历史

方案2:外部保存

方案3:备忘录模式

思维演进过程:

  1. 初级阶段:直接在对象中保存状态

    • 缺点:对象又当运动员又当裁判
  2. 中级阶段:外部对象读取状态保存

    • 缺点:需要暴露内部细节
  3. 高级阶段:对象主动吐出状态胶囊

    • 优点:职责清晰,封装完美

五、思维的局限性反思

并非所有场景都适用备忘录模式:

// 场景1:状态太庞大
class HugeState {
    char pixels[1920*1080*4];  // 8MB图像数据
    // 频繁保存会消耗大量内存
};

// 场景2:状态变化太频繁
class RapidChange {
    // 每秒变化1000次的状态
    // 备忘录模式会造成性能瓶颈
};

// 替代思维:使用命令模式 + 增量存储
class CommandPattern {
    std::vector<Command> commands;  // 只存储操作,不存储状态
    
    void undo() {
        // 反向执行命令,而不是恢复整个状态
    }
};

六、思维的本质总结

备忘录模式的思维精髓可以概括为:

“把对象的记忆从对象身上剥离出来,成为一个独立的、可操作的时间胶囊,让对象可以轻松地进行时空跳跃,同时保持自身的纯粹性和封装性。”

四个核心思维维度:

  1. 分离思维:状态 = 记忆,对象 = 主体,二者分离
  2. 胶囊思维:将状态打包成不可变的时间胶囊
  3. 代理思维:备忘录作为状态的代理,控制访问权限
  4. 时间线思维:将对象的生命周期视为可以回溯的时间线

这种思维模式教会我们:在软件设计中,学会"分离关注点"往往比学会"集成功能"更重要。备忘录模式就是一个完美的"分离"案例。


第三部分:总结

备忘录模式通过将状态存储与业务逻辑分离,既保持了封装性,又提供了灵活的状态管理能力。理解其背后的"记忆外置"、"时间胶囊"和"黑盒封装"三种思维,能够帮助我们更好地在合适的场景应用这一模式。实际开发中,备忘录模式常与命令模式结合实现完整的撤销系统,也可以根据具体需求进行优化(如限制历史记录数量、使用增量存储等)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值