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

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

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

一、什么是备忘录模式?

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

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

流程是这样的:

  1. 在挑战一个强大的BOSS之前,你选择“保存游戏”。游戏主角(发起人)将自己的当前状态打包成一个存档(备忘录)。
  2. 游戏系统(负责人)接收这个存档,并将其保存在一个存档槽里。
  3. 不幸的是,你挑战失败了。你选择“读取存档”。
  4. 游戏系统(负责人)从存档槽里取出之前的存档文件,并将其交还给游戏主角(发起人)。
  5. 游戏主角(发起人)使用这个存档文件,将自己的所有状态恢复到了挑战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_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. 假设的“草稿版本”功能(自定义实现)

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

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

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

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

三、优势与适用场景

优势

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

适用场景

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

结论

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913731.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913731.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Web门户架构的监狱内网改版实践:值班排班系统设计与信创适配探讨

面向监狱内网改版场景的门户平台技术架构与智能排班实践关键词:监狱内网改版、监狱内部网站改版、值班排班系统、信创适配、智能门户架构一、场景背景与问题分析 在信创国产化、等级保护合规、政务集约化趋势持续推进的背景下,传统监狱内部网站普遍面临如…

二分查找篇——在排序数组中查找元素的第一个和最后一个位置【LeetCode】

34. 在排序数组中查找元素的第一个和最后一个位置 一、算法逻辑(逐步通顺讲解每一步思路) 该算法用于在一个升序排列的数组 nums 中查找某个目标值 target 的第一个出现的位置和最后一个出现的位置。 ✅ 1️⃣ 定义 lower_bound 函数 def lower_boun…

【深度学习新浪潮】AI在材料力学领域的研究进展一览

一、材料力学的研究范畴 材料力学是固体力学的核心分支,聚焦于材料在载荷作用下的变形、失效规律及性能优化,其核心任务是揭示材料的强度、刚度和稳定性机制。具体研究内容包括: 基本力学行为:分析杆、梁、轴等结构在拉伸、压缩、弯曲、扭转等载荷下的应力分布与应变响应。…

WPF之命令

命令的定义:命令与事件的区别:命令是具有约束性的。命令还可以控制接收者"先做校验,再保存,再关闭"。命令:WPF的命令,实际上就是实现了ICommand接口的类,平时使用最多的是RoutedComma…

百度文心一言开源大模型ERNIE-4.5-0.3B-PT深度测评

号外号外!6月30号,百度文心一言官宣开源ERNIE 4.5大模型!!! 一收到这个消息,博主就立马从GitCode拉了个模型,本地私有化部署体验了一下,一个字,酷! 鉴于绝大…

零基础,使用Idea工具写一个邮件报警程序

打开idea&#xff0c;创建一个project打开文件目录下的pom.xml文件&#xff0c;添加下面的内容安装依赖&#xff0c;等待下载完成<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId> &…

字体 Unicode 区块字符展示 PDF 生成器

Unicode 字体字符集可视化工具 - 代码介绍 项目概述 这个工具是一个用于分析和可视化字体文件中包含的 Unicode 字符的实用程序&#xff0c;能够扫描指定字体文件&#xff0c;提取其中包含的所有 Unicode 字符&#xff0c;并按 Unicode 区块分类生成 PDF 文档&#xff0c;直观展…

第4章:实战项目一 打造你的第一个AI知识库问答机器人 (RAG)

各位老铁&#xff0c;欢迎来到我们专栏的第一个实战项目。 在过去的三个章节里&#xff0c;我们已经完成了所有的理论储备和环境搭建。我们理解了LLM的本质&#xff0c;掌握了Prompt Engineering的要领&#xff0c;洞悉了Embedding和向量数据库的魔力&#xff0c;并且熟悉了La…

身份证识别api-便捷生活与安全社会的双重保障

身份证识别技术是人工智能和图像处理领域的杰出产物之一&#xff0c;正逐步渗透到我们生活的方方面面。而最直观的作用就是简化身份证验证流程。现如今&#xff0c;无论是银行开户、酒店入住还是政务办理、线上支付&#xff0c;都需要输入 身份证信息进行身份验证&#xff0c;传…

跨国企业进入中国市场:如何利用亚马逊云科技文档 MCP 服务器解决区域差异问题

业务场景 想象一下&#xff0c;您是一家美国科技公司的 IT 架构师&#xff0c;公司刚刚决定将业务扩展到中国市场。作为技术负责人&#xff0c;您需要规划如何将现有的基于亚马逊云科技的应用迁移到中国区域。然而&#xff0c;您很快发现中国区的云服务环境与您熟悉的全球区域…

WPF使用WebBrowser 解决href标签target=_blank在浏览器窗口打开新链接而非窗体内部打开的问题

前言 最近在WPF中使用WebBrowser控件显示网页的时候遇到一个问题,由于网页里面有大规模的连接标签使用了target=_blank的属性,导致打开的网页不是在我们的程序内部,而是调用系统浏览器打开了我们的网页内容,这种情况非常的影响用户体验。于是就有了这篇文章内容。本文将详细…

制作MikTex本地包可用于离线安装包

MikTex安装包版本是basic-miktex-24.1-x64.exe。注&#xff1a;basic版本表示只安装MikTex基本包&#xff0c;不安装全部包。在能够联网的电脑上安装MikTex软件后&#xff0c;可以按以下步骤制作本地包库。一、制作本地包库1、新建一个文件夹&#xff0c;比如在D盘新建miktex-l…

Redis基础的介绍与使用(一)(Redis简介以及Redis下载和安装)

0 引言 本系列用于和大伙儿一起入门Redis&#xff0c;主要包括Redis的下载&#xff0c;分别在终端&#xff0c;图形显示界面以及JAVA代码中进行使用&#xff0c;适合给需要快速了解Redis是什么以及上手使用的朋友们&#xff0c;希望我用最简单的语言来讲清楚相关内容&#xff…

七牛云C++开发面试题及参考答案

智能指针的原理及应用场景是什么&#xff1f; 智能指针是 C 中用于管理动态分配内存的工具&#xff0c;其核心原理是通过 RAII&#xff08;资源获取即初始化&#xff09;技术&#xff0c;将堆内存的生命周期与对象的生命周期绑定&#xff0c;从而避免手动管理内存带来的内存泄…

【Python办公】Excel横板表头转竖版通用工具(GUI版本)横向到纵向的数据重构

目录 专栏导读前言项目概述功能特性技术栈核心代码解析1. 类结构设计2. 界面布局设计3. 滚动列表实现4. 数据转换核心逻辑5. 预览功能实现设计亮点1. 用户体验优化2. 技术实现优势3. 代码结构优势使用场景扩展建议总结完整代码结尾专栏导读 🌸 欢迎来到Python办公自动化专栏—…

C#项目 在Vue/React前端项目中 使用使用wkeWebBrowser引用并且内部使用iframe网页外链 页面部分白屏

如果是使用wkeWebBrowser的引用方式 非常有可能是版本问题导致的 问题分析 1. wkeWebBrowser 的局限性 不支持或不完全支持 ES6 语法&#xff08;如 let, const, Promise, async/await&#xff09; 缺少对现代 Web API 的支持&#xff08;如 Intl, fetch, WebSocket&#xff0…

系统架构设计师论文分享-论微服务架构

我的软考历程 摘要 2023年2月&#xff0c;我所在的公司通过了研发纱线MES系统的立项&#xff0c;该系统为国内纱线工厂提供SAAS服务&#xff0c;旨在提高纱线工厂的数字化和智能化水平。我在该项目中担任系统架构设计师一职&#xff0c;负责该项目的架构设计工作。本文结合我…

The History of Big Data

数据洪流悄然重塑世界的进程中&#xff0c;大数据的历史是技术迭代与需求驱动的交响。从 2003 年分布式系统雏形初现&#xff0c;到 Hadoop 掀起开源浪潮&#xff0c;再到 Spark、容器化技术与深度学习的接力革新&#xff0c;以及 Hadoop 生态的兴衰起落&#xff0c;大数据发展…

【JS逆向基础】数据分析之正则表达式

前言&#xff1a;前面介绍了关于JS逆向所需的基本知识&#xff0c;比如前端三件套等&#xff0c;从这里开始就要进入到数据分析的范围内了&#xff0c;当然对于一些小白而言一些基本的知识还是需要知道的&#xff0c;比如正则&#xff0c;XPATNY与BS4&#xff1b;三个内容用三篇…

Mac mini 高性价比扩容 + Crossover 游戏实测 全流程手册

Mac mini 高性价比扩容 Crossover 游戏实测 全流程手册 本文将图文并茂地指导你如何&#xff1a; 为 M4 Mac mini 外置扩容&#xff08;绿联 USB4 硬盘盒 致态 TiPlus7100&#xff09;安装并配置 Crossover/Whisky 运行 Windows 应用实测游戏运行性能、诊断常见异常一、准备工…