大家好,我是你的Odoo技术伙伴。在开发复杂的业务流程时,我们有时会遇到这样的需求:在对一个对象进行一系列复杂操作之前,保存其当前状态,以便在操作失败或用户希望撤销时,能够一键恢复到操作之前的样子。或者,我们需要追踪一个对象(如一份合同)在不同时间点的所有历史版本。
实现这种“状态快照”和“时光倒流”功能的背后,正是我们今天要探讨的设计模式——备忘录模式(Memento Pattern)。
让我们从一个大家都很熟悉的场景开始:玩电子游戏时的存档。
流程是这样的:
备忘录模式的核心思想是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
关键在于:
在Odoo中,你可能不会显式地去创建一个Memento
类。但是,备忘录模式的思想被巧妙地应用在了几个核心功能中,尤其是那些与历史追踪和版本控制相关的场景。
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)
让我们来分解这个场景:
sale.order
记录。它拥有stage_id
和user_id
等内部状态。mail.tracking.value
模型中创建的一条新记录。这条记录精确地捕获了“哪个字段,从什么旧值,变成了什么新值”。它就是一个包含了部分状态变化的“微型快照”。mail.thread
): 它负责“保管”和“展示”这些备忘录。你在Chatter里看到的“Stage changed from Quotation to Sales Order”这样的消息,就是负责人对备忘录的可视化呈现。write
操作发生时,自动地创建这些备忘录,并将它们与发起人(sale.order
记录)关联起来。这个过程如何体现备忘录模式?
write
)对象前,检测到被追踪字段的变化,并捕获了其新旧值。mail.tracking.value
表中,而不是sale.order
表自身。sale.order
模型并不直接关心这些追踪记录是如何存储的,它只负责在状态变化时,通过_track_subtype
等方法发出一个“需要存档”的信号。Chatter(负责人)也不知道状态变化的具体业务含义,它只负责展示。sale.order
恢复到之前的某个状态。让我们设想一个更贴近经典备忘录模式的自定义场景:为复杂的报价单提供“保存草稿”和“恢复草稿”的功能。
假设我们有一个复杂的报价单,用户在正式发送给客户前,可能会进行多次修改和测算。我们希望提供一个功能,让用户可以随时保存一个“草稿版本”,并在需要时恢复到这个版本。
# 伪代码,用于说明思想
import json
class 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)
这个自定义实现完整地展示了备忘录模式的三个角色及其职责,提供了一个真正的“存档/读档”功能。
tracking
功能)。备忘录模式在Odoo中是一种“幕后英雄”式的设计模式。它不像观察者模式或工厂模式那样随处可见,但它在确保数据可追溯性、提供审计日志、以及构建可恢复操作等方面,提供了坚实的设计思想基础。
Odoo的字段追踪(tracking=True
)功能,就是对备忘录模式最经典、最成功的应用。它自动地为我们捕获、存储和展示了对象状态变化的“备忘录”,极大地提升了系统的透明度和可审计性。
作为Odoo开发者,理解备忘录模式,将帮助你更好地利用Odoo的追踪功能,并在需要实现“撤销”或“版本控制”等高级功能时,为你提供一个清晰、可靠的设计思路。