当前位置: 首页 > news >正文

基于odoo17的设计模式详解---访问模式

大家好,我是你的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.dtcms.com/a/272019.html

相关文章:

  • 构建分布式光伏“四可”能力:支撑新型电力系统安全稳定运行的关键路径
  • 如何在 Ubuntu 上安装 Linux 杀毒软件 ClamAV,排除系统已经感染木马或病毒
  • 设计模式 - 教程
  • 自动驾驶控制系统
  • 低频低压减载装置
  • Go从入门到精通(20)-一个简单web项目-服务搭建
  • 循环神经网络(RNN)Python实现详解
  • 什么是VR实景漫游?VR实景的制作办法?
  • VR博物馆:概念与内涵
  • 广州华锐互动在各领域打造的 VR 成功案例展示​
  • 数字孪生技术引领UI前端设计新趋势:增强现实与虚拟现实的融合应用
  • VBA即用型代码手册:Range对象 Range Object
  • vue3 uniapp 使用ref更新值后子组件没有更新 ref reactive的区别?使用from from -item执行表单验证一直提示没有值
  • 软考(软件设计师)计算机网络-物理层,数据链路层
  • QT - Qvector用法
  • Java设计模式之行为型模式(观察者模式)介绍与说明
  • 关于k8s Kubernetes的10个面试题
  • 【AXI】读重排序深度
  • Scala实现网页数据采集示例
  • linux的用户和权限(学习笔记
  • 西门子200SMART如何无线联三菱FX3U?御控工业网关实现多站点PLC无线通讯集中控制!
  • MiniGPT4源码拆解——models
  • 膨胀卷积介绍
  • QPC框架中状态机的设计优势和特殊之处
  • 大模型在膀胱癌诊疗全流程预测及应用研究报告
  • 【Linux基础命令使用】VIM编辑器的使用
  • 【个人笔记】负载均衡
  • Linux小白学习基础内容
  • LUMP+NFS架构的Discuz论坛部署
  • 可视化DIY小程序工具!开源拖拽式源码系统,自由搭建,完整的源代码包分享