C++ 设计模式《账本事故:当备份被删光那天》

👨‍🎓 模式名称:备忘录模式(Memento)

👦 故事背景:

小明最近在开发一个"收益记录系统"📊,

每天的收入、支出都自动记录到账本中。

他每天都能看到当日盈亏,非常开心:

"今天赚了300块奶茶钱!明天请甜妹喝咖啡☕!"

但有天他手滑写了个bug 💥,把所有账本清零了!

他想恢复昨天的记录,却发现......

他没有一个安全的备份机制,而且账本的内部数据是私有成员变量,

外部系统无法访问。

❌ 没有使用备忘录模式的做法(破坏封装)

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

class Ledger {
public:
    std::vector<std::string> records; // 🚫 暴露内部数据(封装性被破坏)

    void addRecord(const std::string& r) {
        records.push_back(r);
    }

    void show() const {
        std::cout << "📘 当前账本内容:" << std::endl;
        for (auto& r : records)
            std::cout << "  - " << r << std::endl;
    }
};

int main() {
    Ledger ledger;
    ledger.addRecord("收入 +300 元");
    ledger.addRecord("支出 -50 元");

    // 外部直接拷贝整个账本(破坏封装)
    auto backup = ledger.records;

    ledger.addRecord("误操作:清空账本!");
    ledger.records.clear();

    std::cout << "💥 崩溃后账本:" << std::endl;
    ledger.show();

    // 恢复
    ledger.records = backup;

    std::cout << "\n✅ 恢复后的账本:" << std::endl;
    ledger.show();
}

⚠️ 问题分析:

1、破坏封装性:外部直接访问 records,违反了对象设计原则。
2、风险高:外部代码能随意修改账本内容。
3、紧耦合:备份逻辑必须知道内部数据结构。

✅ 使用备忘录模式的正确做法(保持封装性)

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <memory>

// 备忘录类,只能被 Ledger 操作
class LedgerMemento {
    std::vector<std::string> state;
    friend class Ledger; // 仅 Ledger 可访问内部状态
    LedgerMemento(const std::vector<std::string>& s) : state(s) {}
public:
    // 为调试方便,可以提供非破坏性访问(可选)
    // const std::vector<std::string>& getState() const { return state; }
};

// 发起者:账本
class Ledger {
private:
    std::vector<std::string> records; //  私有成员
public:
    void addRecord(const std::string& r) {
        records.push_back(r);
    }

    void clear() {
        records.clear();
    }

    void show() const {
        std::cout << "当前账本内容:" << std::endl;
        if (records.empty()) std::cout << "  (空)" << std::endl;
        for (auto& r : records)
            std::cout << "  - " << r << std::endl;
    }

    // 创建备忘录(封装状态)
    std::shared_ptr<LedgerMemento> createMemento() const {
        return std::shared_ptr<LedgerMemento>(new LedgerMemento(records));
    }

    // 从备忘录恢复状态
    void restore(const std::shared_ptr<LedgerMemento>& memento) {
        if (memento) records = memento->state;
    }
};

// 负责人:管理备份历史
class LedgerCaretaker {
private:
    std::shared_ptr<LedgerMemento> backup;
public:
    void save(const std::shared_ptr<LedgerMemento>& m) {
        backup = m;
    }
    std::shared_ptr<LedgerMemento> getBackup() {
        return backup;
    }
};

// 测试
int main() {
    Ledger ledger;
    LedgerCaretaker caretaker;

    ledger.addRecord("收入 +300 元");
    ledger.addRecord("支出 -50 元");

    // 保存备份
    caretaker.save(ledger.createMemento());

    ledger.addRecord("误操作:清空账本!");
    ledger.clear();

    std::cout << "崩溃后账本:" << std::endl;
    ledger.show();

    // 恢复备份
    ledger.restore(caretaker.getBackup());

    std::cout << "\n恢复后的账本:" << std::endl;
    ledger.show();
    return 0;
}

对比分析

对比项 没用备忘录模式 使用备忘录模式
封装性 ❌ 暴露内部结构 ✅ 内部状态完全封装
安全性 ⚠️ 外部可随意篡改 🔒 只有 Ledger 能访问状态
可维护性 修改内部结构需改外部备份逻辑 内部可自由变更,不影响外部
相关推荐
码匠许师傅7 小时前
【C++ 面试真题】聊聊 C++ 的移动语义与右值引用
java·c++·面试
2603_965148118 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
小小龙学IT9 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
知无不研10 小时前
lambda表达式的使用(3)
开发语言·c++·lambda
cfm_291411 小时前
SpringAI + Ollama 本地大模型
java·开发语言·人工智能·语言模型
C++ 老炮儿的技术栈12 小时前
基于Qt实现轻量化本地音乐播放器
开发语言·c++·qt·c·播放器·音乐
qq_4480111612 小时前
C语言中的柔性数组
c语言·开发语言·柔性数组
小白学大数据12 小时前
长周期爬虫的数据一致性:断点续爬 + 事务回滚保障采集质量
开发语言·爬虫·测试工具
zlinear数据采集卡13 小时前
D223上位机C#开发实战:从帧解析到波形显示的完整实现
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
Poo_Chai13 小时前
QT emit信号后完整处理流程,包括槽函数响应流程
java·开发语言·数据库