设计模式 21 备忘录模式 Memento Pattern

设计模式 21 备忘录模式 Memento Pattern
1.定义

备忘录模式是一种行为型设计模式,它允许你将一个对象的状态保存到一个独立的"备忘录"对象中,并在之后恢复到该状态。

2.内涵

主要用于以下场景:

需要保存对象状态以备恢复: 当你想要在进行一些可能导致对象状态改变的操作之前,先保存对象当前状态,以便在操作失败或需要撤销操作时能够恢复到之前的状态。

需要提供撤销/重做功能: 备忘录模式可以用来实现撤销和重做功能,通过保存一系列状态,用户可以回退到之前的操作步骤。

需要将对象状态序列化: 备忘录模式可以将对象状态序列化到一个独立的对象中,方便存储或传输。

主要模块如下:

  • 发起者(Originator): 需要保存状态的对象。
  • 备忘录(Memento): 保存发起者状态的对象。
  • 守护者(Caretaker): 负责管理备忘录对象,通常会保存多个备忘录,以便实现撤销/重做功能。

解释:

Originator 对象拥有一个 createMemento() 方法,用于创建 Memento 对象并保存自身状态。同时,它也拥有一个 setMemento() 方法,用于从 Memento 对象中恢复自身状态。

Memento 对象保存了 Originator 对象的状态,并且只允许 Originator 对象访问其内部状态。

Caretaker 对象负责管理 Memento 对象,它拥有 addMemento() 和 getMemento() 方法,分别用于添加和获取 Memento 对象。

类图关系:

Originator 对象与 Memento 对象之间存在关联关系,因为 Originator 对象创建并使用 Memento 对象。

Caretaker 对象与 Memento 对象之间也存在关联关系,因为 Caretaker 对象管理 Memento 对象。

3.案例分析
cpp 复制代码
class Memento {
 public:
  virtual ~Memento() {}
  virtual std::string GetName() const = 0;
  virtual std::string date() const = 0;
  virtual std::string state() const = 0;
};

/**
 * The Concrete Memento contains the infrastructure for storing the Originator's
 * state.
 */
class ConcreteMemento : public Memento {
 private:
  std::string state_;
  std::string date_;

 public:
  ConcreteMemento(std::string state) : state_(state) {
    this->state_ = state;
    std::time_t now = std::time(0);
    this->date_ = std::ctime(&now);
  }
  /**
   * The Originator uses this method when restoring its state.
   */
  std::string state() const override {
    return this->state_;
  }
  /**
   * The rest of the methods are used by the Caretaker to display metadata.
   */
  std::string GetName() const override {
    return this->date_ + " / (" + this->state_.substr(0, 9) + "...)";
  }
  std::string date() const override {
    return this->date_;
  }
};

/**
 * The Originator holds some important state that may change over time. It also
 * defines a method for saving the state inside a memento and another method for
 * restoring the state from it.
 */
class Originator {
  /**
   * @var string For the sake of simplicity, the originator's state is stored
   * inside a single variable.
   */
 private:
  std::string state_;

  std::string GenerateRandomString(int length = 10) {
    const char alphanum[] =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    int stringLength = sizeof(alphanum) - 1;

    std::string random_string;
    for (int i = 0; i < length; i++) {
      random_string += alphanum[std::rand() % stringLength];
    }
    return random_string;
  }

 public:
  Originator(std::string state) : state_(state) {
    std::cout << "Originator: My initial state is: " << this->state_ << "\n";
  }
  /**
   * The Originator's business logic may affect its internal state. Therefore,
   * the client should backup the state before launching methods of the business
   * logic via the save() method.
   */
  void DoSomething() {
    std::cout << "Originator: I'm doing something important.\n";
    this->state_ = this->GenerateRandomString(30);
    std::cout << "Originator: and my state has changed to: " << this->state_ << "\n";
  }

  /**
   * Saves the current state inside a memento.
   */
  Memento *Save() {
    return new ConcreteMemento(this->state_);
  }
  /**
   * Restores the Originator's state from a memento object.
   */
  void Restore(Memento *memento) {
    this->state_ = memento->state();
    std::cout << "Originator: My state has changed to: " << this->state_ << "\n";
  }
};

/**
 * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it
 * doesn't have access to the originator's state, stored inside the memento. It
 * works with all mementos via the base Memento interface.
 */
class Caretaker {
  /**
   * @var Memento[]
   */
 private:
  std::vector<Memento *> mementos_;

  /**
   * @var Originator
   */
  Originator *originator_;

 public:
     Caretaker(Originator* originator) : originator_(originator) {
     }

     ~Caretaker() {
         for (auto m : mementos_) delete m;
     }

  void Backup() {
    std::cout << "\nCaretaker: Saving Originator's state...\n";
    this->mementos_.push_back(this->originator_->Save());
  }
  void Undo() {
    if (!this->mementos_.size()) {
      return;
    }
    Memento *memento = this->mementos_.back();
    this->mementos_.pop_back();
    std::cout << "Caretaker: Restoring state to: " << memento->GetName() << "\n";
    try {
      this->originator_->Restore(memento);
    } catch (...) {
      this->Undo();
    }
  }
  void ShowHistory() const {
    std::cout << "Caretaker: Here's the list of mementos:\n";
    for (Memento *memento : this->mementos_) {
      std::cout << memento->GetName() << "\n";
    }
  }
};



/**
 * Client code.
 */

void ClientCode() {
  Originator *originator = new Originator("Super-duper-super-puper-super.");
  Caretaker *caretaker = new Caretaker(originator);
  caretaker->Backup();
  originator->DoSomething();
  caretaker->Backup();
  originator->DoSomething();
  caretaker->Backup();
  originator->DoSomething();
  std::cout << "\n";
  caretaker->ShowHistory();
  std::cout << "\nClient: Now, let's rollback!\n\n";
  caretaker->Undo();
  std::cout << "\nClient: Once more!\n\n";
  caretaker->Undo();

  delete originator;
  delete caretaker;
}

int main() {
  std::srand(static_cast<unsigned int>(std::time(NULL)));
  ClientCode();
  return 0;
}
4.注意事项

备忘录模式在使用时需要注意以下几点:

  1. 备忘录的封装性:

备忘录对象应该封装 Originator 的内部状态,并对外部隐藏这些状态。这意味着 Caretaker 只能通过 Originator 提供的接口来访问备忘录对象,而不能直接访问备忘录对象内部的状态。

这可以防止 Caretaker 意外修改 Originator 的状态,并确保状态的一致性。

  1. 备忘录的管理:

Caretaker 应该负责管理备忘录对象,例如存储、检索和删除备忘录。

Caretaker 应该提供方法来添加、获取和删除备忘录,以便 Originator 可以方便地保存和恢复其状态。

  1. 备忘录的性能:

创建和存储备忘录对象可能会消耗一定的资源,因此需要考虑性能问题。

可以通过使用轻量级的备忘录对象或使用缓存机制来提高性能。

  1. 备忘录的安全性:

备忘录对象可能包含敏感信息,因此需要考虑安全性问题。

可以使用加密或访问控制机制来保护备忘录对象。

  1. 备忘录的使用场景:

备忘录模式适用于需要保存和恢复对象状态的场景,例如撤销/重做功能、游戏存档和恢复等。

需要根据具体场景选择合适的备忘录实现方式,例如使用简单的数据结构或使用更复杂的序列化机制。

5.最佳实践

在使用备忘录模式时,以下是一些值得遵循的经验:

  1. 保持备忘录的简单性:

备忘录对象应该只包含 Originator 状态的必要信息,避免过度复杂化。

尽量使用简单的数据结构来存储状态,例如字典、列表等,避免使用复杂的自定义对象。

  1. 使用工厂模式创建备忘录:

使用工厂模式可以简化备忘录对象的创建过程,并隐藏备忘录对象的具体实现细节。

工厂模式可以根据不同的场景创建不同的备忘录对象,例如创建不同的备忘录类型来保存不同的状态信息。

  1. 使用策略模式管理备忘录:

使用策略模式可以根据不同的需求选择不同的备忘录管理策略,例如使用不同的存储方式或不同的访问控制机制。

策略模式可以提高代码的可扩展性和灵活性,方便根据需求进行修改和扩展。

  1. 使用缓存机制提高性能:

可以使用缓存机制来存储最近使用的备忘录对象,避免重复创建备忘录对象。

缓存机制可以提高性能,尤其是在频繁保存和恢复状态的场景下。

  1. 使用序列化机制保存备忘录:

可以使用序列化机制将备忘录对象保存到文件或数据库中,以便持久化保存状态。

序列化机制可以方便地保存和恢复状态,并支持跨平台使用。

  1. 考虑安全性问题:

如果备忘录对象包含敏感信息,需要考虑安全性问题,例如使用加密或访问控制机制来保护备忘录对象。

可以使用安全策略来限制对备忘录对象的访问权限,并确保信息的安全性。

6.总结

备忘录模式是一种强大的设计模式,可以帮助我们实现状态的保存和恢复功能。在使用备忘录模式时,需要注意封装性、管理、性能、安全性以及使用场景等方面,以确保其有效性和安全性。

相关推荐
晨米酱15 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机20 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机21 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机21 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机21 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤1 天前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式