备忘录模式

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

银行账户

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

  1. class BankAccount
  2. {
  3. int balance = 0;
  4. public:
  5. explicit BankAccount(const int balance): balance(balance) {}
  6. Memento deposit(int amount)
  7. {
  8. balance += amount;
  9. return { balance };
  10. }
  11. void restore(const Memento& m)
  12. {
  13. balance = m.balance;
  14. }
  15. };

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

  1. class Memento
  2. {
  3. int balance;
  4. public:
  5. Memento(int balance): balance(balance)
  6. {
  7. }
  8. friend class BankAccount;
  9. };

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

  • Memento类是不可变的。想象一下,如果我们可以更改余额, 那么我们可以将帐户回滚到它从未处于的状态。

  • 备忘录将BankAccount声明为一个友元类。这允许帐户实际使用balance字段。另一个同样有效的替代方法是将Memento作为BankAccount的内部类。

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

  1. void memento()
  2. {
  3. BankAccount ba{ 100 };
  4. auto m1 = ba.deposit(50);
  5. auto m2 = ba.deposit(25);
  6. cout << ba << "\n"; // Balance: 175
  7. // undo to m1
  8. ba.restore(m1);
  9. cout << ba << "\n"; // Balance: 150
  10. // redo
  11. ba.restore(m2);
  12. cout << ba << "\n"; // Balance: 175
  13. }

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

撤销与回滚

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

  1. class BankAccount2 // supports undo/redo
  2. {
  3. int balance = 0;
  4. vector<shared_ptr<Memento>> changes;
  5. int current;
  6. public:
  7. explicit BankAccount2(const int balance) : balance(balance)
  8. {
  9. changes.emplace_back(make_shared<Memento>(balance));
  10. current = 0;
  11. }

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

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

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

  1. shared_ptr<Memento> deposit(int amount)
  2. {
  3. balance += amount;
  4. auto m = make_shared<Memento>(balance);
  5. changes.push_back(m);
  6. ++current;
  7. return m;
  8. }

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

  1. void restore(const shared_ptr<Memento>& m)
  2. {
  3. if (m)
  4. {
  5. balance = m->balance;
  6. changes.push_back(m);
  7. current = changes.size() - 1;
  8. }
  9. }

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

下面是undo()的实现:

  1. shared_ptr<Memento> undo()
  2. {
  3. if (current > 0)
  4. {
  5. --current;
  6. auto m = changes[current];
  7. balance = m->balance;
  8. return m;
  9. }
  10. return{};
  11. }

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

redo()的实现如下:

  1. shared_ptr<Memento> redo()
  2. {
  3. if (current + 1 < changes.size())
  4. {
  5. ++current;
  6. auto m = changes[current];
  7. balance = m->balance;
  8. return m;
  9. }
  10. return{};
  11. }

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

  1. BankAccount2 ba{ 100 };
  2. ba.deposit(50);
  3. ba.deposit(25); // 125
  4. cout << ba << "\n";
  5. ba.undo();
  6. cout << "Undo 1: " << ba << "\n"; // Undo 1: 150
  7. ba.undo();
  8. cout << "Undo 2: " << ba << "\n"; // Undo 2: 100
  9. ba.redo();
  10. cout << "Redo 2: " << ba << "\n"; // Redo 2: 150
  11. ba.undo(); // back to 100 again