基于odoo17的设计模式详解---中介模式

大家好,我是你的Odoo技术伙伴。在复杂的业务场景中,对象之间的交互往往会变得错综复杂,形成一张难以维护的“蜘蛛网”式的依赖关系。每个对象都需要了解许多其他对象,任何一个小小的改动都可能引发连锁反应。

为了解决这个问题,软件设计领域引入了中介者模式(Mediator Pattern)。今天,我们将深入探讨这一模式,并揭示Odoo 17是如何在不显式声明“Mediator”类的情况下,将其中介思想融入其核心架构,从而优雅地管理复杂模块间的交互。

一、什么是中介者模式?

让我们先从一个经典的现实世界比喻开始:机场的空中交通管制塔台

想象一下,如果没有塔台,每架准备起降的飞机都需要直接与其他所有飞机通信,以协调航线、跑道和时间。这将是一个灾难性的、混乱的通信网络。

而有了塔台(中介者 Mediator),情况就完全不同了:

  • 所有飞机(同事对象 Colleague)只与塔台通信。
  • 塔台负责协调所有飞机的行动,确保它们不会发生冲突。
  • 飞机之间不需要彼此了解,它们只需要遵守塔台的指令。

将这个比喻转换成软件设计的语言:

中介者模式用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

它的核心思想是:将网状的多对多通信,转变为星状的一对多通信

二、Odoo中的中介者:无形胜有形

与状态模式和观察者模式一样,Odoo并没有一个名为 models.Mediator 的基类。相反,中介者模式的思想渗透在Odoo的多个核心概念中,它是一种架构层面的实现。

在Odoo中,以下几个角色经常扮演中介者的角色:

  • 父模型(Parent Model):在One2many关系中,父模型(如 sale.order)经常充当其子记录行(如 sale.order.line)的中介者。
  • 向导(Wizards / TransientModel):向导是为完成特定、复杂任务而生的临时中介者,它协调用户输入和后台多个业务对象的操作。
  • 表单视图控制器(Form Controller):在前端,表单控制器协调着页面上所有字段(Widgets)之间的交互和数据同步。

下面我们逐一剖析。

1. 父模型:业务逻辑的协调中心

这是Odoo中最常见、最强大的中介者模式应用。以我们熟悉的 sale.ordersale.order.line 为例:

  • 同事对象 (Colleagues): 每一个 sale.order.line 记录。
  • 中介者 (Mediator): sale.order 记录。
交互场景分析:

价格计算与汇总:

  • 当一个订单行(sale.order.line)的数量或单价改变时,它自己只负责计算自己的小计(price_subtotal)。它并不关心其他订单行的价格,也不直接去更新订单总额。
  • 通过观察者模式(@api.depends),这个变化会通知到它的中介者——sale.order
  • sale.order(中介者)收到通知后,会执行 _compute_amount_all 方法。在这个方法里,它会遍历所有的订单行(同事们),汇总它们的小计,最终计算出 amount_total
  • 思考:如果没有 sale.order 这个中介,要计算总额,每个 sale.order.line 在变化时,可能都需要去获取它所有的兄弟节点,然后执行一遍求和逻辑。这会导致订单行之间产生不必要的耦合。

全局操作的协调:

  • 当用户点击销售订单上的“Confirm”按钮时,触发的是 sale.orderaction_confirm 方法。
  • 这个方法作为中介者,会统一协调所有订单行的后续操作。比如,它可能会遍历所有 sale.order.line,为每一个行创建相应的库存移动(stock.move)或触发采购规则。
  • 订单行本身并不需要知道“何时”以及“如何”创建库存移动,它们只需要被动地接受来自中介者(sale.order)的指令。

通过这种方式,sale.order 承担了所有订单行的协调工作,sale.order.line 则保持了其业务的单纯性,极大地降低了系统的复杂度。

2. 向导(Wizard):任务导向的临时中介者

Odoo的向导(基于 models.TransientModel)是中介者模式的典范。它们是为执行一个跨越多模型、需要用户输入的复杂操作而设计的临时对象。

经典案例:批量创建发票向导

当你从销售订单列表视图中选择多个订单,然后点击“创建发票”时,会弹出一个向导。

  • 同事对象 (Colleagues): 你选择的多个 sale.order 记录,以及即将被创建的 account.move(发票)记录。
  • 中介者 (Mediator): “创建发票”向导实例(如 sale.advance.payment.inv)。
交互流程:
  1. 启动: Odoo创建一个向导实例,并将选中的 sale.order 的ID列表传递给它(通常在 context 中)。

  2. 协调用户输入: 向导的视图会展示一些选项,比如“创建普通发票”还是“定金发票”,以及定金的比例等。这些选项是创建发票所必需的、但 sale.order 本身不包含的信息。

  3. 执行动作: 用户填写完信息并点击“创建并查看发票”后,向导的某个方法被调用。

  4. 中介与协调: 在这个方法里,向导(中介者)会:

    • 读取用户输入的参数。
    • 根据 context 中的ID,获取所有需要处理的 sale.order 记录。
    • 遍历这些销售订单,调用 sale.order 模型的相关方法(如 _create_invoices)来生成发票。
    • 最后,它可能会返回一个 action,将用户导航到新创建的发票列表。

在这个过程中,sale.orderaccount.move 之间没有直接交互。所有的协调工作,包括数据收集、逻辑调用、流程控制,都由这个临时的向导中介者完成。任务结束后,向导的记录通常会被自动清理。

3. 表单视图控制器:UI层面的交互中介

在Odoo的前端JS框架(Owl)中,每个表单视图都有一个对应的 FormController。它充当了页面上所有UI组件(我们称之为Widget,现在更多是Component)的中介。

经典案例:@api.onchange

当你在表单上改变一个字段的值时(比如,在销售订单行上选择一个产品),会发生什么?

  • 同事对象 (Colleagues): product_id 字段的组件,name(描述)字段的组件,price_unit(单价)字段的组件等。
  • 中介者 (Mediator): FormController 和后端的 onchange 机制。
交互流程:
  1. 用户在 product_id 组件中选择了一个产品。
  2. 该组件通知 FormController(中介者)它的值发生了变化。
  3. FormController(中介者)并不会直接去告诉 name 组件或 price_unit 组件去更新。相反,它会收集当前表单上所有字段的值,通过RPC调用后端的 onchange 方法。
  4. 后端的 onchange 方法(如 _onchange_product_id)执行业务逻辑,计算出需要更新的字段(如 nameprice_unit)的新值,并将其返回。
  5. FormController(中介者)接收到返回的更新数据后,再去通知 name 组件和 price_unit 组件更新它们自己的显示值。

在这个流程中,product_id 组件无需知道它需要影响哪些其他组件。它只需向中介者报告自己的变化。所有的协调和数据流转都由中介者统一处理,使得UI组件之间高度解耦。

四、中介者模式的优势与风险

优势

  • 降低耦合度 (Decoupling): 将网状依赖变为星状依赖,同事对象之间不再有直接联系,维护和修改变得更加容易。
  • 集中控制逻辑 (Centralized Control): 复杂的交互逻辑被封装在中介者内部,使得逻辑更加清晰,易于理解和管理。
  • 提高复用性 (Increased Reusability): 同事对象因为不依赖其他同事,所以更容易被复用。一个 sale.order.line 模型理论上可以被用在不同的中介者下(比如用在采购订单行上,如果设计允许的话)。

潜在风险

  • 中介者膨胀 (Mediator Bloat / God Object): 最大的风险是中介者自身可能变得异常复杂,承担了过多的责任,成为一个难以维护的“上帝对象”。在Odoo中,一个拥有几千行代码的父模型或向导就是这种风险的体现。
  • 应对策略: 设计时要有意识地保持中介者的职责单一,只负责“协调”。如果某段业务逻辑本身很复杂,应该将其封装在对应的同事对象的方法中,中介者只负责在合适的时机调用它,而不是自己实现所有细节。

结论

中介者模式是Odoo架构设计的基石之一。它并非以一个具体的类存在,而是作为一种核心思想,体现在父子模型的关系、向导的设计以及前端控制层中。

理解Odoo如何运用中介者模式,可以帮助我们:

  • 看懂Odoo核心模块复杂业务背后的清晰结构。
  • 在自己的定制开发中,自觉地利用父模型或向导来充当协调者,避免写出混乱、高耦合的代码。
  • 在面对复杂的交互需求时,优先思考“是否需要一个中介者?”,而不是让对象之间直接对话。

你可能感兴趣的:(设计模式)