模式定义:
可以 实现 在其他对象中 保存 当前对象的状态 和恢复对象到 过去的某个状态;
这个 状态 指 对象中的所有属性;
生活中的例子:
- 代码编辑器 如 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中无法实现 对 备忘录内容访问和修改 的 限制; 那么 也没必要增加 备忘录角色,完全 可以 结合原型模式,并将备忘录的功能 放到 管理者角色中实现; 这样减少了类的数量,理解更加容易;
相关链接: