大家好,我是你的Odoo技术伙伴。想象一下,我们有一个复杂的对象结构,比如一个由不同类型的订单行(销售行、折扣行、备注行)组成的销售订单。现在,我们需要对这个结构执行一些新的操作,比如:

  1. 生成一份详细的PDF报价单。
  2. 将其数据导出为一种特殊的XML格式,以对接外部系统。
  3. 计算其中所有“实体产品”行的总重量。

如果我们将这些操作方法直接添加到订单行和订单的模型类中,会导致这些模型类越来越臃肿,违反了单一职责原则。更糟糕的是,每当需要一个新的操作时,我们都得去修改这些核心的业务模型。

为了解决这个问题,软件设计领域引入了一种非常精巧的模式——访问者模式(Visitor Pattern)

一、什么是访问者模式?

让我们用一个旅行的例子来理解它:

  • 对象结构(Object Structure): 一个城市,里面有各种不同类型的景点,如博物馆(Element A)公园(Element B)历史遗迹(Element C)
  • 访问者(Visitor): 你,一个旅行者。

现在,不同类型的旅行者(访问者)来到这个城市,他们对景点的“操作”是不同的:

  • 一个历史学家(Visitor 1):
    • 在博物馆,他会花大量时间研究文物(visit_museum())。
    • 在公园,他可能只是匆匆走过(visit_park())。
    • 在历史遗迹,他会进行详细的考古笔记(visit_historic_site())。
  • 一个摄影师(Visitor 2):
    • 在博物馆,他可能只对建筑光影感兴趣。
    • 在公园,他会寻找最佳的自然风光拍摄角度。
    • 在历史遗迹,他会专注于捕捉残垣断壁的沧桑感。

关键在于:

  1. 景点(对象结构)是稳定的:城市不会因为来了一个摄影师就改变自己的结构。
  2. 操作是多变的: 我们可以随时“派遣”一个新的访问者(比如一个美食家)来对这个城市进行全新的操作(寻找美食)。
  3. 双重分派(Double Dispatch): 当一个访问者访问一个景点时,最终执行的动作由“访问者的类型”和“景点的类型”两者共同决定。

转换成软件设计的语言:

访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下,定义作用于这些元素的新操作。

二、Odoo中的访问者模式:报表引擎与数据处理

在Odoo中,访问者模式的思想主要体现在那些需要处理异构对象结构(heterogeneous object structures)并对其执行复杂操作的场景中,最典型的就是报表引擎数据序列化/导出

场景:生成销售订单的PDF报表

Odoo的报表系统(基于QWeb引擎)是访问者模式的一个绝佳范例。

  • 对象结构(Object Structure): sale.order记录及其关联的sale.order.line记录集。这个记录集是异构的,因为订单行可能是普通的产品行,也可能是用于分组的“章节(Section)”行或纯文本的“备注(Note)”行。
  • 元素(Elements): 每个sale.order.line记录。
  • 访问者(Visitor): QWeb报表模板(.xml文件)。这个模板本身就是一个包含了“如何处理(渲染)”不同类型元素逻辑的“访问者”。

让我们看一个简化的QWeb模板:

<!-- a_module/reports/sale_order_report.xml -->
<template id="report_saleorder_document"><t t-call="web.html_container"><t t-foreach="docs" t-as="doc"> <!-- 'doc' is a sale.order record --><!-- ... 报表头 ... --><table><thead>...</thead><tbody><!-- 遍历对象结构中的每个元素 (order_line) --><t t-foreach="doc.order_line" t-as="line"><!-- “双重分派”:根据元素的类型,执行不同的访问/渲染逻辑 --><!-- 访问者对“章节”类型的元素的操作 --><tr t-if="line.display_type == 'line_section'"><td colspan="99"><strong><span t-field="line.name"/></strong></td></tr><!-- 访问者对“备注”类型的元素的操作 --><tr t-if="line.display_type == 'line_note'"><td colspan="99"><span t-field="line.name"/></td></tr><!-- 访问者对“普通产品”类型的元素的操作 --><tr t-if="not line.display_type"><td><span t-field="line.product_id.name"/></td><td><span t-field="line.product_uom_qty"/></td><!-- ... 其他列 ... --></tr></t></tbody></table></t></t>
</template>

这个过程如何体现访问者模式?

  1. 稳定的对象结构: sale.ordersale.order.line的模型定义是稳定的。我们为了生成一份新的报表样式,完全不需要去修改它们的Python代码。
  2. 分离的操作: 报表的渲染逻辑(如何将一个订单行显示在PDF上)被完全封装在了QWeb模板(访问者)中,与模型的核心业务逻辑分离。
  3. 轻松添加新操作: 如果我们想创建一个新的、完全不同格式的报表(比如一个简化的内部成本核算表),我们只需要创建一个新的QWeb模板(一个新的访问者),而无需触碰任何Python模型。这个新访问者可以有自己的一套全新的逻辑来“访问”和“解读”sale.ordersale.order.line
  4. 双重分派的体现: t-if语句的判断 line.display_type == '...',实际上就是在模拟双重分派。QWeb引擎(作为调用者)将模板(访问者)应用于line(元素),而最终的渲染结果取决于line的类型。

另一个例子:数据导出/序列化

当我们需要将Odoo中的一个复杂对象(如包含多层嵌套的物料清单BoM)导出为一个特定的JSON或XML格式时,也可以应用访问者模式。

我们可以创建一个BomJsonVisitor类,它有visit_bom(bom)visit_bom_line(line)等方法。然后我们写一个遍历函数,它接受一个BoM对象和一个Visitor对象,递归地遍历BoM树,并在每个节点上调用visitor.visit_...(node)

这样,如果我们将来需要导出为XML,只需再创建一个BomXmlVisitor即可,而核心的遍历逻辑和BoM模型都无需改动。

三、优势与适用场景

优势

  1. 符合开闭原则: 可以在不修改现有对象结构的情况下,轻松地添加新的操作。这对于像Odoo这样需要高度可扩展性的系统来说至关重要。
  2. 集中相关操作: 将一个特定操作(如PDF渲染)的所有相关逻辑都集中在一个访问者类中,而不是分散在各个元素类里,使得代码更加内聚。
  3. 操作复杂结构: 访问者模式非常适合用于处理复杂的、由不同类型对象组成的树形或复合结构。

注意事项

  1. 破坏封装性(潜在风险): 为了让访问者能够执行操作,元素类通常需要暴露一些其内部状态的接口,这在某种程度上可能会破坏其封装性。
  2. 对象结构难以修改: 访问者模式的优点是易于添加新操作,但其代价是难以添加新的元素类型。如果你的对象结构(比如sale.order.linedisplay_type)经常需要增加新的类型,那么每个已有的访问者(QWeb模板)都需要被修改以支持这个新类型,这会违反开闭原则。

因此,访问者模式最适用于:对象结构相对稳定,但需要频繁地为其定义新操作的场景。 Odoo的报表系统正是这样一个完美的场景。

结论

访问者模式是一种优雅的、用于实现功能与数据结构分离的设计模式。在Odoo中,它虽然不常以显式的Visitor类出现,但其核心思想——将操作逻辑从被操作的对象中抽离出来——在QWeb报表引擎等模块中得到了淋漓尽致的体现。

通过将渲染逻辑封装在QWeb模板(访问者)中,Odoo允许我们自由地为同一套稳定的数据模型(如sale.order)创建出无数种不同的视图(报表),而无需对核心业务代码进行任何侵入式修改。

作为Odoo开发者,理解访问者模式,将帮助你更好地设计可扩展的数据处理和展现功能。当你遇到一个需求,需要对一个复杂的、稳定的对象结构进行多种不同的、未来可能还会增加的“解读”或“操作”时,访问者模式将为你提供一个强大而优雅的设计思路。

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

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

相关文章

使用langgraph 构建RAG 智能问答代理

RAG 智能问答代理&#xff1a; ✅ 支持用户持续提问 ✅ 根据模型判断是否需要查资料 ✅ 自动调用 PDF 检索工具查找内容 ✅ 自动引用内容回答 ✅ 可以输入 exit / quit 退出 下载需要的library pip install langchain-google-genai pip install langgraph pip install langchai…

零基础搭建监控系统:Grafana+InfluxDB 保姆级教程,5分钟可视化服务器性能!​

​​你是否遇到过这些问题&#xff1f;​​服务器突然卡顿&#xff0c;却找不到性能瓶颈需要手动查看日志&#xff0c;无法实时监控数据运维报表全靠截图拼接&#xff0c;领导直呼“太原始”​今天教你用GrafanaInfluxDB构建企业级监控系统&#xff0c;从此告别“盲人摸象”式运…

Java中的wait和notify、Condition接口的使用

Java中的wait和notify机制基础概念在Java中&#xff0c;wait()和notify()是Object类的原生方法&#xff0c;用于实现线程间的协作&#xff1a;wait()使当前线程释放对象锁并进入等待状态必须在synchronized代码块内调用语法&#xff1a;obj.wait() 或 obj.wait(long timeout)线…

【Modern C++ Part9】Prefer-alias-declarations-to-typedefs

条款9&#xff1a;优先使用声明别名而不是typedef 我有信心说&#xff0c;大家都同意使用STL容器是个好的想法&#xff0c;并且我希望&#xff0c;条款18可以说服你使用std::unique_ptr也是个好想法&#xff0c;但是我想绝对我们中间没有人喜欢写像这样std::unique_ptr<std:…

STM32第二十一天定时器TIM

1 定时器基础知识a:上来说就是用来定时的机器&#xff0c;是存在于STM32单片机中的一个外设。STM32总共有8个定时器&#xff0c;分别是2个高级定时器(TIM1、TIM8)&#xff0c;4个通用定时器 (TIM2、TIM3、TIM4、TIM5) 和2个基本定时器 (TIM6、TIM7)&#xff0c;如下图所示&…

鼎捷T100程序开发:校验程序详解

校验程序概述 T100系统校验程序需要确保系统数据的准确性、完整性和一致性&#xff0c;相当于企业信息系统的"健康体检医生"。它通过预设规则扫描系统数据&#xff0c;识别异常和错误&#xff0c;确保业务运行可靠。通过持续完善的校验机制&#xff0c;企业能够构建数…

BaseDao 通用查询方法设计与实现

BaseDao 通用查询方法设计与实现 一、通用查询方法设计思路 1. 核心查询功能矩阵查询类型方法名功能说明复杂度主键查询findById()根据主键获取单个实体⭐全量查询findAll()获取全部实体⭐条件查询findByCondition()动态条件查询⭐⭐⭐分页查询findPage()分页结果集⭐⭐⭐⭐排序…

llama.cpp gguf主要量化方法

量化是一种通过降低模型参数的表示精度来减少模型的大小和计算存储需求的方法&#xff0c;如把单精度fp32转化为int8来减少存储和计算成本。 常见的是线性量化&#xff0c;公式 r S(q-Z)&#xff0c;将实数值r映射为量化的整数值q&#xff0c;其中缩放因子S和零点Z根据参数分…

汽车级MCU选型新方向:eVTOL垂桨控制监控芯片的替代选型技术分析

摘要&#xff1a;随着eVTOL&#xff08;电动垂直起降航空器&#xff09;领域的蓬勃发展&#xff0c;对于高性能、高可靠性的垂桨控制监控芯片的需求日益迫切。本文旨在深入探讨汽车级MCU&#xff08;微控制单元&#xff09;在这一新兴领域的应用潜力&#xff0c;以国科安芯推出…

Deepoc具身智能大模型:送餐机器人如何学会“读心术”

Deepoc具身智能大模型&#xff1a;送餐机器人如何学会“读心术”深夜十点的商场火锅店&#xff0c;一台银色机器人正穿越喧闹的人群。当它感知到奔跑的儿童突然变向&#xff0c;驱动轮立即反向微调0.3度&#xff1b;托盘上的牛油锅底因顾客推椅产生晃动&#xff0c;平衡系统瞬间…

学习设计模式《十七》——状态模式

一、基础概念 状态模式的本质是【根据状态来分离和选择行为】。 状态模式的定义&#xff1a;允许一个对象在其内部状态改变时改变它的行为&#xff1b;对象看起来似乎修改了它的类。 认识状态模式序号认识状态模式说明1状态和行为通常指的是对象实例的属性的值&#xff1b;而行…

python的婚纱影楼管理系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 随着婚纱…

滤波电路Multisim电路仿真实验汇总——硬件工程师笔记

目录 1 滤波电路基础知识 1.1 滤波电路的分类 1.1.1 按频率选择性分类 1.1.2 按实现方式分类 1.2 滤波电路的设计 1.2.1 确定滤波器类型 1.2.2 计算截止频率 1.2.3 选择滤波阶数 1.2.4 考虑元件参数 1.2.5 仿真验证 1.3 滤波电路的应用 1.3.1 电源滤波 1.3.2 音频…

C++随机打乱函数:简化源码与原理深度剖析

文章目录一、Fisher-Yates洗牌算法核心原理二、std::random_shuffle简化实现与缺陷分析简化源码&#xff08;核心逻辑&#xff09;原理层面的致命缺陷三、std::shuffle的现代改进与实现简化源码&#xff08;核心逻辑&#xff09;原理层面的关键改进四、随机数生成器工作原理URB…

DBeaver连接MySQL8.0报错Public Key Retrieval is not allowed

DBeaver 链接本地mysql8.0服务报错Public Key Retrieval is not allowed为什么会出现这个错误&#xff1f;MySQL 8.0 默认使用新的认证插件&#xff1a;caching_sha2_password某些客户端&#xff08;比如老版本的 JDBC 驱动或配置不当的 DBeaver&#xff09;在连接时&#xff0…

SpringBoot系列—统一功能处理(拦截器)

上篇文章&#xff1a; SpringBoot系列—MyBatis-plushttps://blog.csdn.net/sniper_fandc/article/details/148979284?fromshareblogdetail&sharetypeblogdetail&sharerId148979284&sharereferPC&sharesourcesniper_fandc&sharefromfrom_link 目录 1 拦…

《汇编语言:基于X86处理器》第7章 整数运算(3)

本章将介绍汇编语言最大的优势之一:基本的二进制移位和循环移位技术。实际上&#xff0c;位操作是计算机图形学、数据加密和硬件控制的固有部分。实现位操作的指令是功能强大的工具&#xff0c;但是高级语言只能实现其中的一部分&#xff0c;并且由于高级语言要求与平台无关&am…

应用笔记|数字化仪在医学SS-OCT中的应用

引言近些年来&#xff0c;OCT&#xff08;光学相干断层扫描&#xff0c;Optical Coherence Tomography&#xff09;作为一种非破坏性3D光学成像技术逐渐在医学眼科设备中流行起来。OCT可提供实时一维深度或二维截面或三维立体的图像&#xff0c;分辨率可达微米&#xff08;μm&…

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议 在2025年的技术环境下&#xff0c;Ubuntu 22.04和24.04 LTS各有优势&#xff0c;选择哪一个取决于具体应用场景和用户需求。经过对系统内核、桌面环境、软件生态、生命周期支持等多方面因素的综合分析&#xff0c;本报告将…

Linux进程的生命周期:状态定义、转换与特殊场景

前言 在Linux系统中&#xff0c;进程是资源分配和调度的基本单位&#xff0c;而进程状态则是理解进程行为的关键。从运行中的任务&#xff08;TASK_RUNNING&#xff09;到僵尸进程&#xff08;EXIT_ZOMBIE&#xff09;&#xff0c;每个状态都反映了进程在内核调度、资源等待或父…