大家好,我是你的Odoo技术伙伴。在开发复杂的业务流程时,我们有时会遇到这样的需求:在对一个对象进行一系列复杂操作之前,保存其当前状态,以便在操作失败或用户希望撤销时,能够一键恢复到操作之前的样子。或者,我们需要追踪一个对象(如一份合同)在不同时间点的所有历史版本。
实现这种“状态快照”和“时光倒流”功能的背后,正是我们今天要探讨的设计模式——备忘录模式(Memento Pattern)。
一、什么是备忘录模式?
让我们从一个大家都很熟悉的场景开始:玩电子游戏时的存档。
- 你(发起人 Originator): 游戏中的主角,拥有各种状态(生命值、等级、位置、装备)。
- 游戏存档文件(备忘录 Memento): 一个包含了你当前所有状态的“快照”。这个文件本身可能是一个加密的二进制文件,你无法直接看懂或修改它的内容。
- 游戏系统(负责人 Caretaker): 负责管理所有的存档文件。它可以让你创建新存档、读取旧存档,但它不关心存档文件里的具体内容。
流程是这样的:
- 在挑战一个强大的BOSS之前,你选择“保存游戏”。游戏主角(发起人)将自己的当前状态打包成一个存档(备忘录)。
- 游戏系统(负责人)接收这个存档,并将其保存在一个存档槽里。
- 不幸的是,你挑战失败了。你选择“读取存档”。
- 游戏系统(负责人)从存档槽里取出之前的存档文件,并将其交还给游戏主角(发起人)。
- 游戏主角(发起人)使用这个存档文件,将自己的所有状态恢复到了挑战BOSS之前的样子。
备忘录模式的核心思想是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
关键在于:
- 封装性: 只有发起人自己知道如何创建和恢复备忘录。负责人和备忘录本身都无法访问或修改状态的细节。
- 状态隔离: 对象的状态被提取出来,独立于对象本身进行存储和管理。
二、Odoo中的备忘录模式:追踪与审计的基石
在Odoo中,你可能不会显式地去创建一个Memento
类。但是,备忘录模式的思想被巧妙地应用在了几个核心功能中,尤其是那些与历史追踪和版本控制相关的场景。
1. 字段追踪 (tracking=True
) 与 Chatter
这是Odoo中备忘录模式最直观、最普遍的应用。当你为一个字段设置tracking=True
时,你就启动了一个针对该字段的“自动存档”系统。
class SaleOrder(models.Model):_inherit = 'sale.order'# 当 stage_id 字段的值发生变化时,系统会自动创建一个“备忘录”stage_id = fields.Many2one('sale.order.stage', string='Stage', tracking=True)user_id = fields.Many2one('res.users', string='Salesperson', tracking=True)
让我们来分解这个场景:
- 发起人(Originator):
sale.order
记录。它拥有stage_id
和user_id
等内部状态。 - 备忘录(Memento): 当字段值变化时,
mail.tracking.value
模型中创建的一条新记录。这条记录精确地捕获了“哪个字段,从什么旧值,变成了什么新值”。它就是一个包含了部分状态变化的“微型快照”。 - 负责人(Caretaker):
- Chatter (
mail.thread
): 它负责“保管”和“展示”这些备忘录。你在Chatter里看到的“Stage changed from Quotation to Sales Order”这样的消息,就是负责人对备忘录的可视化呈现。 - Odoo的ORM和事务系统: 它们负责在
write
操作发生时,自动地创建这些备忘录,并将它们与发起人(sale.order
记录)关联起来。
- Chatter (
这个过程如何体现备忘录模式?
- 状态捕获: Odoo ORM在保存(
write
)对象前,检测到被追踪字段的变化,并捕获了其新旧值。 - 外部存储: 这个状态变化信息被存储在独立的
mail.tracking.value
表中,而不是sale.order
表自身。 - 封装性:
sale.order
模型并不直接关心这些追踪记录是如何存储的,它只负责在状态变化时,通过_track_subtype
等方法发出一个“需要存档”的信号。Chatter(负责人)也不知道状态变化的具体业务含义,它只负责展示。 - 恢复(概念上): 虽然Odoo的Chatter主要用于审计和追踪,不提供一键“恢复”功能,但它完整地保存了历史状态。一个开发者可以基于这些“备忘录”(追踪记录),编写一个手动的方法来将
sale.order
恢复到之前的某个状态。
2. 假设的“草稿版本”功能(自定义实现)
让我们设想一个更贴近经典备忘录模式的自定义场景:为复杂的报价单提供“保存草稿”和“恢复草稿”的功能。
假设我们有一个复杂的报价单,用户在正式发送给客户前,可能会进行多次修改和测算。我们希望提供一个功能,让用户可以随时保存一个“草稿版本”,并在需要时恢复到这个版本。
# 伪代码,用于说明思想
import jsonclass Quotation(models.Model):_name = 'sale.quotation'_inherit = ['mail.thread']# ... 报价单的各种字段 ...order_line = fields.One2many(...)# 负责人(Caretaker)的一部分:存储备忘录的地方memento_ids = fields.One2many('sale.quotation.memento', 'quotation_id')def create_memento(self, name):"""发起人(Originator)创建备忘录的方法"""self.ensure_one()# 1. 捕获内部状态state_snapshot = {'note': self.note,'payment_term_id': self.payment_term_id.id,'lines': [line.read()[0] for line in self.order_line]}# 2. 创建备忘录对象,但将状态封装在json字段中# 备忘录本身不知道这些数据的具体含义self.env['sale.quotation.memento'].create({'name': name,'quotation_id': self.id,'state_data': json.dumps(state_snapshot)})def restore_from_memento(self, memento):"""发起人(Originator)从备忘录恢复状态的方法"""self.ensure_one()# 1. 从备忘录获取状态数据state_snapshot = json.loads(memento.state_data)# 2. 恢复自身状态# 只有发起人自己知道如何解读和应用这些数据self.order_line.unlink() # 先清空旧的行self.write({'note': state_snapshot.get('note'),'payment_term_id': state_snapshot.get('payment_term_id'),'order_line': [(0, 0, line_vals) for line_vals in state_snapshot.get('lines', [])]})class QuotationMemento(models.Model):_name = 'sale.quotation.memento'_description = 'Quotation Snapshot (Memento)'name = fields.Char('Version Name')quotation_id = fields.Many2one('sale.quotation')# 备忘录的核心:存储状态,但不暴露其内部结构state_data = fields.Text('State Data (JSON)', readonly=True)def action_restore(self):"""负责人的一个动作,触发恢复"""self.quotation_id.restore_from_memento(self)
这个自定义实现完整地展示了备忘录模式的三个角色及其职责,提供了一个真正的“存档/读档”功能。
三、优势与适用场景
优势
- 保护封装性: 将对象的状态快照功能,从对象本身的核心业务逻辑中分离出来。状态的保存和恢复细节由发起人自己控制,外部世界(负责人)无法篡改备忘录的内部。
- 简化发起人: 发起人不需要关心状态的存储和管理,它只需要在需要时创建备忘录或从备忘录中恢复即可,职责更加单一。
- 高内聚,松耦合: 备忘录模式提供了一种状态恢复的实现机制,而客户端(负责人)与这个机制是松耦合的。
适用场景
- 需要提供一个可撤销(Undo)或可回滚(Rollback)操作的场景。
- 需要对一个对象的历史版本进行追踪和审计时(如Odoo的
tracking
功能)。 - 当需要保存的内部状态非常复杂,不希望将这些状态直接暴露给外部时。
结论
备忘录模式在Odoo中是一种“幕后英雄”式的设计模式。它不像观察者模式或工厂模式那样随处可见,但它在确保数据可追溯性、提供审计日志、以及构建可恢复操作等方面,提供了坚实的设计思想基础。
Odoo的字段追踪(tracking=True
)功能,就是对备忘录模式最经典、最成功的应用。它自动地为我们捕获、存储和展示了对象状态变化的“备忘录”,极大地提升了系统的透明度和可审计性。
作为Odoo开发者,理解备忘录模式,将帮助你更好地利用Odoo的追踪功能,并在需要实现“撤销”或“版本控制”等高级功能时,为你提供一个清晰、可靠的设计思路。