备忘录模式(Snapshot)及代码实现

2022-06-18 09:18:18

模式定义:

可以 实现 在其他对象中 保存 当前对象的状态 和恢复对象到 过去的某个状态;

这个 状态 指 对象中的所有属性;

生活中的例子:

  • 代码编辑器 如 pycharm等等 在编写错误后,可以通过 回退快捷键(ctrl+z)实现 撤回操作;
  • 上线前,对数据库进行备份,在上线失败后,通过 备份回滚数据库...
  • 小游戏的 存档功能, 在 打boss失败后,直接 读取之前 的存档,继续打boss;

何时使用此模式:

  • 在 需要 对象的中间状态进行保存和恢复或回滚之前状态的情况时 可以使用此模式; 如 需求为  实现一个编辑器,可以 撤回到之前的操作....

该模式关键的角色:

  • 发起人角色(Originator): 是被保存的对象; 此角色 实现 业务方法,和 声明 创建备份和 恢复数据 的 方法; 但是 这2个方法的实际实现 由 备忘录完成;
  • 备忘录角色(Memento): 负责 对 被保存对象(发起人角色) 进行属性 数据提取,并 添加 版本号,备份实际 等额外信息;  此角色 只负责 把 被保存对象进行 打包; 实际 备份 到 列表或硬盘等 由 管理者角色完成;
  • 管理者角色(Caretaker): 负责 管理 备忘录;包括 存储,和 恢复 备份数据;及 查看 备忘录历史信息等;

该模式的主要优缺点如下:

优点:

  • 此模式最大特点是 实现了 可以恢复到对象之前某个状态的功能; 这样可以让用户也能 吃上 甜美的后悔药;
  • 符合单一职责原则; 发起人角色 只专注于自身的业务逻辑;不用保存和管理 备份对象; 这些备份对象的信息 封装在 备忘录中, 由 管理者角色 进行保存和管理;

缺点:

  • 备份对象保存在内存中,如果频繁 保存 导致内存激增,资源被消耗; 可以通过将 备份对象或数据 通过 Pickle等等方法 保存在硬盘中,需要时 再读取,从而解决此问题;
  • python 中 无法保证 备忘录中的 数据不被修改;因为 python时动态编程语言,且对 类的 私有方法或属性 的 外部调用 也是不会报错的;

和 其他模式 的 比较:

  • 原型模式: 可以相互合作, 把需要备份的 对象 进行克隆后,直接保存 克隆后的对象, 简单方便;
  • 命令模式: 可以相互合作, 命令模式 用于实现不同的操作; 备忘录 用于 备份命令执行前的状态;

示例代码部分:

# -*- coding: utf-8 -*-
"""
(C) rgc
All rights reserved
create time '2021/1/7 19:42'

Usage:
使用 备忘录模式 实现 数据库备份恢复功能
"""
from copy import deepcopy
from datetime import datetime


class UserDataBase:
    """
    用户数据库 类
    发起人角色
    包括 业务数据操作方法,存储 和 恢复数据 方法(这2个方法的实现通过 快照类实现)
    """

    def __init__(self):
        """
        初始化
        """
        self.user_list = []

    def add_user(self, user):
        """
        向用户list中添加用户
        :param user: 
        :return: 
        """
        self.user_list.append(user)
        print(f'新增用户{user}成功')

    def del_user(self, user):
        """
        从用户list中删除用户
        :param user:
        :return:
        """
        if user in self.user_list:
            self.user_list.remove(user)
        print('删除成功')

    def snapshot(self):
        """
        快照数据
        其实 存储的是 快照对象,里面包括了 用户数据库对象 和 版本号
        :return:
        """
        print('用户数据 快照成功')
        return SnapShot(self)

    def restore(self, snapshot_obj):
        """
        恢复数据
        从 快照对象 中的 用户数据库对象 属性 恢复数据
        :param snapshot_obj
        :return:
        """
        self.user_list = snapshot_obj.data.user_list
        print('恢复数据成功')


class SnapShot:
    """
    快照 类
    备忘录角色
    实现 对 需要快照数据的包装(打版本号,提取 具体备份的数据,此处直接备份整个对象)
    """

    def __init__(self, user_db_obj: UserDataBase):
        """
        初始化数据
        :param user_db_obj:
        """
        # 快照的数据
        self.data = deepcopy(user_db_obj)
        # 快照版本号
        self.version = str(datetime.now())[:19]

    def get_version(self):
        """
        获取 快照版本号
        :return:
        """
        return self.version


class DBManager:
    """
    数据库管理员 类
    管理者 角色
    管理 数据库 何时备份,恢复, 以及 存储快照
    """

    def __init__(self, user_db_obj: UserDataBase):
        """

        :param user_db_obj:
        """
        self.snapshot_list = []
        self.user_db_obj = user_db_obj

    def backup(self):
        """
        备份 用户数据库
        :return:
        """
        self.snapshot_list.append(self.user_db_obj.snapshot())
        print('备份成功')

    def rollback(self):
        """
        回滚 用户数据库到上个版本
        :return:
        """
        if not self.snapshot_list:
            print('已经是最早版本,无法回滚')
        snapshot = self.snapshot_list.pop()
        self.user_db_obj.restore(snapshot)
        print(f'回滚到版本为:{snapshot.get_version()} 成功')

    def show_snapshot_history_info(self):
        """
        显示 快照历史信息
        :return:
        """
        for item in self.snapshot_list:
            print('版本号为', item.version)


if __name__ == '__main__':
    user_db_obj = UserDataBase()
    db_manager_obj = DBManager(user_db_obj)

    print('新增2个用户,然后备份', '*' * 10)
    user_db_obj.add_user(1)
    user_db_obj.add_user(2)
    db_manager_obj.backup()

    print('删除一个用户', '*' * 10)
    user_db_obj.del_user(2)
    print(f'最新用户列表为:{user_db_obj.user_list}')

    print('恢复被删除的用户', '*' * 10)
    db_manager_obj.rollback()
    print(f'恢复后的用户列表为:{user_db_obj.user_list}')

总结:

Python中 此模式的管理者角色 无法限制 对 备忘录内容的 访问和修改,因为 是动态编程语言; 所以 此模式的定义中 没有说  允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态;

既然 Python中无法实现 对 备忘录内容访问和修改 的 限制; 那么 也没必要增加 备忘录角色,完全 可以 结合原型模式,并将备忘录的功能 放到 管理者角色中实现; 这样减少了类的数量,理解更加容易;

相关链接:

https://refactoringguru.cn/design-patterns/memento

http://c.biancheng.net/view/1400.html

  • 作者:rgc_520_zyl
  • 原文链接:https://blog.csdn.net/rgc_520_zyl/article/details/112254192
    更新时间:2022-06-18 09:18:18