登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 122|回复: 0

C++设计模式18.备忘录模式

[复制链接]

68

主题

2

回帖

91

积分

网站编辑

积分
91
发表于 2024-12-29 13:16:12 | 显示全部楼层 |阅读模式

备忘录模式

首先看一个例子来认识备忘录模式。

银行账户

下面是一个银行账户的类, 现在我们决定只使用deposit()函数创建一个银行账户。在前面的例子中,它是void的,而现在,deposit()将返回一个Memento。然后Memento可以回滚帐户到以前的状态:

class BankAccount
{ 
    int balance = 0;
    public: 
    explicit BankAccount(const int balance): balance(balance) {}
    Memento deposit(int amount)
    { 
        balance += amount;
        return { balance };
    }

    void restore(const Memento& m)
    { 
        balance = m.balance;
    } 
};

至于Memento本身,我们可以采用一个简单的实现方法:

class Memento
{ 
    int balance;
    public: 
        Memento(int balance): balance(balance)
    { 
    } 
    friend class BankAccount;
};

这里有两件事需要特别注意:

  • Memento类是不可变的。想象一下,如果我们可以更改余额, 那么我们可以将帐户回滚到它从未处于的状态。
  • 备忘录将BankAccount声明为一个友元类。这允许帐户实际使用balance字段。另一个同样有效的替代方法是将Memento作为BankAccount的内部类。

下面是备忘录模式的具体使用:

void memento()
{ 
    BankAccount ba{ 100 };
    auto m1 = ba.deposit(50);
    auto m2 = ba.deposit(25);
    cout << ba << "\n"; // Balance: 175
    // undo to m1
    ba.restore(m1);
    cout << ba << "\n"; // Balance: 150

    // redo
    ba.restore(m2);
    cout << ba << "\n"; // Balance: 175
}

这个实现已经足够好了,只是缺少了一些东西。例如,您永远不会得到表示开盘余额的Memento,因为构造函数不能返回值。

撤销与回滚

如果我们要存储每一个由银行帐户产生的Memento,该怎么办呢? 我们将引入一个新的银行账户类BankAccount2,它将保存它生成的每一个Memento:

class BankAccount2 // supports undo/redo
{ 
    int balance = 0;
    vector<shared_ptr<Memento>> changes;
    int current;
    public: 
    explicit BankAccount2(const int balance) : balance(balance)
    { 
        changes.emplace_back(make_shared<Memento>(balance));
        current = 0;
    }

我们现在已经解决了返回初始余额的问题:初始变化的Memento也被存储。当然,这个Memento实际上并没有返回,所以为了回滚到它,我想您可以实现一些reset()函数——这完全取决于您。

我们使用shared_ptr来存储memento,也使用shared_ptr来返回它们。此外,我们使用当前字段作为进入更改列表的“指针”,如果我们决定撤消并后退一步,我们总是可以redo和恢复到我们之前的状态。

下面我们来看一下deposit()的实现:

shared_ptr<Memento> deposit(int amount)
{ 
    balance += amount;
    auto m = make_shared<Memento>(balance);
    changes.push_back(m);
    ++current;
    return m;
}

我们添加了一个方法, 该方法基于一个Memento来恢复帐户状态:

void restore(const shared_ptr<Memento>& m)
{ 
    if (m)
    { 
        balance = m->balance;
        changes.push_back(m);
        current = changes.size() - 1;
    } 
}

恢复的过程与我们之前看到的明显不同。首先,我们实际上检查shared_ptr是否已初始化。此外,当我们恢复一个memento时,我们实际上是将该memento推入更改列表,以使得一个undo操作能够正确地执行。

下面是undo()的实现:

shared_ptr<Memento> undo()
{ 
    if (current > 0)
    { 
        --current;
        auto m = changes[current];
        balance = m->balance;
        return m;
    }
    return{};
}

只有当current大于零时,我们才能使用undo()。此时,我们将指针后移并获取相应的changes成员,然后返回对应的balance。如果不能回滚到以前的memento,则返回一个默认构造的shared_ptr。

redo()的实现如下:

shared_ptr<Memento> redo()
{  
    if (current + 1 < changes.size())
    {  
        ++current;
        auto m = changes[current];
        balance = m->balance;
        return m;
    }
    return{};
}

同样,我们需要能够执行一些redo:如果可以,我们可以安全地redo,如果不行,我们什么都不做,并返回一个空指针。把它们结合起来,我们现在可以开始使用undo/redo功能了:

BankAccount2 ba{ 100 };
ba.deposit(50);
ba.deposit(25); // 125
cout << ba << "\n";
ba.undo();
cout << "Undo 1: " << ba << "\n"; // Undo 1: 150
ba.undo();
cout << "Undo 2: " << ba << "\n"; // Undo 2: 100
ba.redo();
cout << "Redo 2: " << ba << "\n"; // Redo 2: 150
ba.undo(); // back to 100 again
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-1-19 02:34 , Processed in 0.057698 second(s), 27 queries .

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表