• 设计模式 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.案例分析
    1. class Memento {
    2.  public:
    3.   virtual ~Memento() {}
    4.   virtual std::string GetName() const = 0;
    5.   virtual std::string date() const = 0;
    6.   virtual std::string state() const = 0;
    7. };
    8. /**
    9.  * The Concrete Memento contains the infrastructure for storing the Originator's
    10.  * state.
    11.  */
    12. class ConcreteMemento : public Memento {
    13.  private:
    14.   std::string state_;
    15.   std::string date_;
    16.  public:
    17.   ConcreteMemento(std::string state) : state_(state) {
    18.     this->state_ = state;
    19.     std::time_t now = std::time(0);
    20.     this->date_ = std::ctime(&now);
    21.   }
    22.   /**
    23.    * The Originator uses this method when restoring its state.
    24.    */
    25.   std::string state() const override {
    26.     return this->state_;
    27.   }
    28.   /**
    29.    * The rest of the methods are used by the Caretaker to display metadata.
    30.    */
    31.   std::string GetName() const override {
    32.     return this->date_ + " / (" + this->state_.substr(0, 9) + "...)";
    33.   }
    34.   std::string date() const override {
    35.     return this->date_;
    36.   }
    37. };
    38. /**
    39.  * The Originator holds some important state that may change over time. It also
    40.  * defines a method for saving the state inside a memento and another method for
    41.  * restoring the state from it.
    42.  */
    43. class Originator {
    44.   /**
    45.    * @var string For the sake of simplicity, the originator's state is stored
    46.    * inside a single variable.
    47.    */
    48.  private:
    49.   std::string state_;
    50.   std::string GenerateRandomString(int length = 10) {
    51.     const char alphanum[] =
    52.         "0123456789"
    53.         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    54.         "abcdefghijklmnopqrstuvwxyz";
    55.     int stringLength = sizeof(alphanum) - 1;
    56.     std::string random_string;
    57.     for (int i = 0; i < length; i++) {
    58.       random_string += alphanum[std::rand() % stringLength];
    59.     }
    60.     return random_string;
    61.   }
    62.  public:
    63.   Originator(std::string state) : state_(state) {
    64.     std::cout << "Originator: My initial state is: " << this->state_ << "\n";
    65.   }
    66.   /**
    67.    * The Originator's business logic may affect its internal state. Therefore,
    68.    * the client should backup the state before launching methods of the business
    69.    * logic via the save() method.
    70.    */
    71.   void DoSomething() {
    72.     std::cout << "Originator: I'm doing something important.\n";
    73.     this->state_ = this->GenerateRandomString(30);
    74.     std::cout << "Originator: and my state has changed to: " << this->state_ << "\n";
    75.   }
    76.   /**
    77.    * Saves the current state inside a memento.
    78.    */
    79.   Memento *Save() {
    80.     return new ConcreteMemento(this->state_);
    81.   }
    82.   /**
    83.    * Restores the Originator's state from a memento object.
    84.    */
    85.   void Restore(Memento *memento) {
    86.     this->state_ = memento->state();
    87.     std::cout << "Originator: My state has changed to: " << this->state_ << "\n";
    88.   }
    89. };
    90. /**
    91.  * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it
    92.  * doesn't have access to the originator's state, stored inside the memento. It
    93.  * works with all mementos via the base Memento interface.
    94.  */
    95. class Caretaker {
    96.   /**
    97.    * @var Memento[]
    98.    */
    99.  private:
    100.   std::vector mementos_;
    101.   /**
    102.    * @var Originator
    103.    */
    104.   Originator *originator_;
    105.  public:
    106.      Caretaker(Originator* originator) : originator_(originator) {
    107.      }
    108.      ~Caretaker() {
    109.          for (auto m : mementos_) delete m;
    110.      }
    111.   void Backup() {
    112.     std::cout << "\nCaretaker: Saving Originator's state...\n";
    113.     this->mementos_.push_back(this->originator_->Save());
    114.   }
    115.   void Undo() {
    116.     if (!this->mementos_.size()) {
    117.       return;
    118.     }
    119.     Memento *memento = this->mementos_.back();
    120.     this->mementos_.pop_back();
    121.     std::cout << "Caretaker: Restoring state to: " << memento->GetName() << "\n";
    122.     try {
    123.       this->originator_->Restore(memento);
    124.     } catch (...) {
    125.       this->Undo();
    126.     }
    127.   }
    128.   void ShowHistory() const {
    129.     std::cout << "Caretaker: Here's the list of mementos:\n";
    130.     for (Memento *memento : this->mementos_) {
    131.       std::cout << memento->GetName() << "\n";
    132.     }
    133.   }
    134. };
    135. /**
    136.  * Client code.
    137.  */
    138. void ClientCode() {
    139.   Originator *originator = new Originator("Super-duper-super-puper-super.");
    140.   Caretaker *caretaker = new Caretaker(originator);
    141.   caretaker->Backup();
    142.   originator->DoSomething();
    143.   caretaker->Backup();
    144.   originator->DoSomething();
    145.   caretaker->Backup();
    146.   originator->DoSomething();
    147.   std::cout << "\n";
    148.   caretaker->ShowHistory();
    149.   std::cout << "\nClient: Now, let's rollback!\n\n";
    150.   caretaker->Undo();
    151.   std::cout << "\nClient: Once more!\n\n";
    152.   caretaker->Undo();
    153.   delete originator;
    154.   delete caretaker;
    155. }
    156. int main() {
    157.   std::srand(static_cast<unsigned int>(std::time(NULL)));
    158.   ClientCode();
    159.   return 0;
    160. }

    4.注意事项

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

    1. 备忘录的封装性:

    备忘录对象应该封装 Originator 的内部状态,并对外部隐藏这些状态。这意味着 Caretaker 只能通过 Originator 提供的接口来访问备忘录对象,而不能直接访问备忘录对象内部的状态。
    这可以防止 Caretaker 意外修改 Originator 的状态,并确保状态的一致性。


    2. 备忘录的管理:

    Caretaker 应该负责管理备忘录对象,例如存储、检索和删除备忘录。
    Caretaker 应该提供方法来添加、获取和删除备忘录,以便 Originator 可以方便地保存和恢复其状态。


    3. 备忘录的性能:

    创建和存储备忘录对象可能会消耗一定的资源,因此需要考虑性能问题。
    可以通过使用轻量级的备忘录对象或使用缓存机制来提高性能。


    4. 备忘录的安全性:

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


    5. 备忘录的使用场景:

    备忘录模式适用于需要保存和恢复对象状态的场景,例如撤销/重做功能、游戏存档和恢复等。
    需要根据具体场景选择合适的备忘录实现方式,例如使用简单的数据结构或使用更复杂的序列化机制。

    5.最佳实践

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

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

    备忘录对象应该只包含 Originator 状态的必要信息,避免过度复杂化。
    尽量使用简单的数据结构来存储状态,例如字典、列表等,避免使用复杂的自定义对象。
    2. 使用工厂模式创建备忘录:

    使用工厂模式可以简化备忘录对象的创建过程,并隐藏备忘录对象的具体实现细节。
    工厂模式可以根据不同的场景创建不同的备忘录对象,例如创建不同的备忘录类型来保存不同的状态信息。
    3. 使用策略模式管理备忘录:

    使用策略模式可以根据不同的需求选择不同的备忘录管理策略,例如使用不同的存储方式或不同的访问控制机制。
    策略模式可以提高代码的可扩展性和灵活性,方便根据需求进行修改和扩展。
    4. 使用缓存机制提高性能:

    可以使用缓存机制来存储最近使用的备忘录对象,避免重复创建备忘录对象。
    缓存机制可以提高性能,尤其是在频繁保存和恢复状态的场景下。
    5. 使用序列化机制保存备忘录:

    可以使用序列化机制将备忘录对象保存到文件或数据库中,以便持久化保存状态。
    序列化机制可以方便地保存和恢复状态,并支持跨平台使用。
    6. 考虑安全性问题:

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

    6.总结

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

  • 相关阅读:
    14 异常处理 & 日志
    Spring - IoC 容器之 Bean 的生命周期
    如何管好一个迭代?让数据帮你回答这些关键问题
    交易机器人-微信群通知
    计算机毕业设计ssm+vue基本微信小程序的“香草屋”饮料奶茶点单小程序
    Java每日一练
    Vue学习笔记(二)
    illustrator插件-将位图矢量化-转为SVG-AI插件
    体验版小程序访问不到后端接口请求失败问题解决方案
    Netty01 - 第一个网络通信程序
  • 原文地址:https://blog.csdn.net/jxusthusiwen/article/details/139273997