基于odoo17的设计模式详解---备忘模式

大家好,我是你的Odoo技术伙伴。在开发复杂的业务流程时,我们有时会遇到这样的需求:在对一个对象进行一系列复杂操作之前,保存其当前状态,以便在操作失败或用户希望撤销时,能够一键恢复到操作之前的样子。或者,我们需要追踪一个对象(如一份合同)在不同时间点的所有历史版本。

实现这种"状态快照"和"时光倒流"功能的背后,正是我们今天要探讨的设计模式------备忘录模式(Memento Pattern)

一、什么是备忘录模式?

让我们从一个大家都很熟悉的场景开始:玩电子游戏时的存档

  • 你(发起人 Originator): 游戏中的主角,拥有各种状态(生命值、等级、位置、装备)。
  • 游戏存档文件(备忘录 Memento): 一个包含了你当前所有状态的"快照"。这个文件本身可能是一个加密的二进制文件,你无法直接看懂或修改它的内容。
  • 游戏系统(负责人 Caretaker): 负责管理所有的存档文件。它可以让你创建新存档、读取旧存档,但它不关心存档文件里的具体内容。

流程是这样的:

  1. 在挑战一个强大的BOSS之前,你选择"保存游戏"。游戏主角(发起人)将自己的当前状态打包成一个存档(备忘录)。
  2. 游戏系统(负责人)接收这个存档,并将其保存在一个存档槽里。
  3. 不幸的是,你挑战失败了。你选择"读取存档"。
  4. 游戏系统(负责人)从存档槽里取出之前的存档文件,并将其交还给游戏主角(发起人)。
  5. 游戏主角(发起人)使用这个存档文件,将自己的所有状态恢复到了挑战BOSS之前的样子。

备忘录模式 的核心思想是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

关键在于:

  • 封装性: 只有发起人自己知道如何创建和恢复备忘录。负责人和备忘录本身都无法访问或修改状态的细节。
  • 状态隔离: 对象的状态被提取出来,独立于对象本身进行存储和管理。

二、Odoo中的备忘录模式:追踪与审计的基石

在Odoo中,你可能不会显式地去创建一个Memento类。但是,备忘录模式的思想被巧妙地应用在了几个核心功能中,尤其是那些与历史追踪版本控制相关的场景。

1. 字段追踪 (tracking=True) 与 Chatter

这是Odoo中备忘录模式最直观、最普遍的应用。当你为一个字段设置tracking=True时,你就启动了一个针对该字段的"自动存档"系统。

python 复制代码
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_iduser_id等内部状态。
  • 备忘录(Memento) : 当字段值变化时,mail.tracking.value模型中创建的一条新记录。这条记录精确地捕获了"哪个字段,从什么旧值,变成了什么新值"。它就是一个包含了部分状态变化的"微型快照"。
  • 负责人(Caretaker) :
    • Chatter (mail.thread): 它负责"保管"和"展示"这些备忘录。你在Chatter里看到的"Stage changed from Quotation to Sales Order"这样的消息,就是负责人对备忘录的可视化呈现。
    • Odoo的ORM和事务系统 : 它们负责在write操作发生时,自动地创建这些备忘录,并将它们与发起人(sale.order记录)关联起来。

这个过程如何体现备忘录模式?

  • 状态捕获 : Odoo ORM在保存(write)对象前,检测到被追踪字段的变化,并捕获了其新旧值。
  • 外部存储 : 这个状态变化信息被存储在独立的mail.tracking.value表中,而不是sale.order表自身。
  • 封装性 : sale.order模型并不直接关心这些追踪记录是如何存储的,它只负责在状态变化时,通过_track_subtype等方法发出一个"需要存档"的信号。Chatter(负责人)也不知道状态变化的具体业务含义,它只负责展示。
  • 恢复(概念上) : 虽然Odoo的Chatter主要用于审计和追踪,不提供一键"恢复"功能,但它完整地保存了历史状态。一个开发者可以基于这些"备忘录"(追踪记录),编写一个手动的方法来将sale.order恢复到之前的某个状态。

2. 假设的"草稿版本"功能(自定义实现)

让我们设想一个更贴近经典备忘录模式的自定义场景:为复杂的报价单提供"保存草稿"和"恢复草稿"的功能。

假设我们有一个复杂的报价单,用户在正式发送给客户前,可能会进行多次修改和测算。我们希望提供一个功能,让用户可以随时保存一个"草稿版本",并在需要时恢复到这个版本。

python 复制代码
# 伪代码,用于说明思想
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)

这个自定义实现完整地展示了备忘录模式的三个角色及其职责,提供了一个真正的"存档/读档"功能。

三、优势与适用场景

优势

  1. 保护封装性: 将对象的状态快照功能,从对象本身的核心业务逻辑中分离出来。状态的保存和恢复细节由发起人自己控制,外部世界(负责人)无法篡改备忘录的内部。
  2. 简化发起人: 发起人不需要关心状态的存储和管理,它只需要在需要时创建备忘录或从备忘录中恢复即可,职责更加单一。
  3. 高内聚,松耦合: 备忘录模式提供了一种状态恢复的实现机制,而客户端(负责人)与这个机制是松耦合的。

适用场景

  • 需要提供一个可撤销(Undo)或可回滚(Rollback)操作的场景。
  • 需要对一个对象的历史版本进行追踪和审计时(如Odoo的tracking功能)。
  • 当需要保存的内部状态非常复杂,不希望将这些状态直接暴露给外部时。

结论

备忘录模式在Odoo中是一种"幕后英雄"式的设计模式。它不像观察者模式或工厂模式那样随处可见,但它在确保数据可追溯性、提供审计日志、以及构建可恢复操作等方面,提供了坚实的设计思想基础。

Odoo的字段追踪(tracking=True)功能,就是对备忘录模式最经典、最成功的应用。它自动地为我们捕获、存储和展示了对象状态变化的"备忘录",极大地提升了系统的透明度和可审计性。

作为Odoo开发者,理解备忘录模式,将帮助你更好地利用Odoo的追踪功能,并在需要实现"撤销"或"版本控制"等高级功能时,为你提供一个清晰、可靠的设计思路。

相关推荐
有想法的py工程师28 分钟前
PostgreSQL 锁等待监控,查找等待中的锁
数据库
学不会就看28 分钟前
Django--02模型和管理站点
数据库·oracle·django
←か淡定☆ ヾ1 小时前
SQL Server 2008R2 到 2012 数据库迁移完整指南
数据库·sql server
瀚高PG实验室1 小时前
Arcgis连接HGDB报错
数据库·arcgis·瀚高数据库
IT小辉同学2 小时前
PostgreSQL 与 MySQL 获取字段注释并转换为驼峰命名教程
数据库·mysql·postgresql
小小寂寞的城2 小时前
JAVA观察者模式demo【设计模式系列】
java·观察者模式·设计模式
xinghunzhiye20102 小时前
redis升级
数据库·redis·缓存
一只fish3 小时前
MySQL 8.0 OCP 1Z0-908 题目解析(21)
数据库·mysql
涛思数据(TDengine)3 小时前
时序数据库 TDengine × SSRS:专为工业、能源场景打造的报表解决方案
大数据·数据库·物联网·时序数据库·tdengine
打鱼又晒网3 小时前
Lecture #20:Database Logging
数据库