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 能访问状态
可维护性 修改内部结构需改外部备份逻辑 内部可自由变更,不影响外部
相关推荐
千里马-horse18 小时前
BigInt
开发语言·bigint·napi·addon
Robot侠18 小时前
从 Python 到 Ollama:将微调后的 Llama-3/Qwen 一键导出为 GGUF
开发语言·python·llama·qwen
刺客-Andy18 小时前
JS中级面试题 50道及答案
开发语言·javascript·ecmascript
Java小白笔记18 小时前
BigDecimal用法示例
java·开发语言·spring boot
l1t18 小时前
Python 字符串反转方法
linux·开发语言·python
Eiceblue18 小时前
使用 Python 写入多类型数据至 Excel 文件
开发语言·python·excel
luquinn18 小时前
用canvas切图展示及标记在原图片中的位置
开发语言·前端·javascript
程序员阿鹏18 小时前
OOM是如何解决的?
java·开发语言·jvm·spring
爱潜水的小L18 小时前
自学嵌入式day37,网络编程
开发语言·网络·php
阿蒙Amon18 小时前
C#每日面试题-类和结构的区别
开发语言·c#