大家好,我是你的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.order
和 sale.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.order
的action_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
)。
交互流程:
-
启动: Odoo创建一个向导实例,并将选中的
sale.order
的ID列表传递给它(通常在context
中)。 -
协调用户输入: 向导的视图会展示一些选项,比如“创建普通发票”还是“定金发票”,以及定金的比例等。这些选项是创建发票所必需的、但
sale.order
本身不包含的信息。 -
执行动作: 用户填写完信息并点击“创建并查看发票”后,向导的某个方法被调用。
-
中介与协调: 在这个方法里,向导(中介者)会:
- 读取用户输入的参数。
- 根据
context
中的ID,获取所有需要处理的sale.order
记录。 - 遍历这些销售订单,调用
sale.order
模型的相关方法(如_create_invoices
)来生成发票。 - 最后,它可能会返回一个
action
,将用户导航到新创建的发票列表。
在这个过程中,sale.order
和 account.move
之间没有直接交互。所有的协调工作,包括数据收集、逻辑调用、流程控制,都由这个临时的向导中介者完成。任务结束后,向导的记录通常会被自动清理。
3. 表单视图控制器:UI层面的交互中介
在Odoo的前端JS框架(Owl)中,每个表单视图都有一个对应的 FormController
。它充当了页面上所有UI组件(我们称之为Widget,现在更多是Component)的中介。
经典案例:@api.onchange
当你在表单上改变一个字段的值时(比如,在销售订单行上选择一个产品),会发生什么?
- 同事对象 (Colleagues):
product_id
字段的组件,name
(描述)字段的组件,price_unit
(单价)字段的组件等。 - 中介者 (Mediator):
FormController
和后端的onchange
机制。
交互流程:
- 用户在
product_id
组件中选择了一个产品。 - 该组件通知
FormController
(中介者)它的值发生了变化。 FormController
(中介者)并不会直接去告诉name
组件或price_unit
组件去更新。相反,它会收集当前表单上所有字段的值,通过RPC调用后端的onchange
方法。- 后端的
onchange
方法(如_onchange_product_id
)执行业务逻辑,计算出需要更新的字段(如name
、price_unit
)的新值,并将其返回。 FormController
(中介者)接收到返回的更新数据后,再去通知name
组件和price_unit
组件更新它们自己的显示值。
在这个流程中,product_id
组件无需知道它需要影响哪些其他组件。它只需向中介者报告自己的变化。所有的协调和数据流转都由中介者统一处理,使得UI组件之间高度解耦。
四、中介者模式的优势与风险
优势
- 降低耦合度 (Decoupling): 将网状依赖变为星状依赖,同事对象之间不再有直接联系,维护和修改变得更加容易。
- 集中控制逻辑 (Centralized Control): 复杂的交互逻辑被封装在中介者内部,使得逻辑更加清晰,易于理解和管理。
- 提高复用性 (Increased Reusability): 同事对象因为不依赖其他同事,所以更容易被复用。一个
sale.order.line
模型理论上可以被用在不同的中介者下(比如用在采购订单行上,如果设计允许的话)。
潜在风险
- 中介者膨胀 (Mediator Bloat / God Object): 最大的风险是中介者自身可能变得异常复杂,承担了过多的责任,成为一个难以维护的“上帝对象”。在Odoo中,一个拥有几千行代码的父模型或向导就是这种风险的体现。
- 应对策略: 设计时要有意识地保持中介者的职责单一,只负责“协调”。如果某段业务逻辑本身很复杂,应该将其封装在对应的同事对象的方法中,中介者只负责在合适的时机调用它,而不是自己实现所有细节。
结论
中介者模式是Odoo架构设计的基石之一。它并非以一个具体的类存在,而是作为一种核心思想,体现在父子模型的关系、向导的设计以及前端控制层中。
理解Odoo如何运用中介者模式,可以帮助我们:
- 看懂Odoo核心模块复杂业务背后的清晰结构。
- 在自己的定制开发中,自觉地利用父模型或向导来充当协调者,避免写出混乱、高耦合的代码。
- 在面对复杂的交互需求时,优先思考“是否需要一个中介者?”,而不是让对象之间直接对话。
大家好,我是你的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.order
和 sale.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.order
的action_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
)。
交互流程:
-
启动: Odoo创建一个向导实例,并将选中的
sale.order
的ID列表传递给它(通常在context
中)。 -
协调用户输入: 向导的视图会展示一些选项,比如“创建普通发票”还是“定金发票”,以及定金的比例等。这些选项是创建发票所必需的、但
sale.order
本身不包含的信息。 -
执行动作: 用户填写完信息并点击“创建并查看发票”后,向导的某个方法被调用。
-
中介与协调: 在这个方法里,向导(中介者)会:
- 读取用户输入的参数。
- 根据
context
中的ID,获取所有需要处理的sale.order
记录。 - 遍历这些销售订单,调用
sale.order
模型的相关方法(如_create_invoices
)来生成发票。 - 最后,它可能会返回一个
action
,将用户导航到新创建的发票列表。
在这个过程中,sale.order
和 account.move
之间没有直接交互。所有的协调工作,包括数据收集、逻辑调用、流程控制,都由这个临时的向导中介者完成。任务结束后,向导的记录通常会被自动清理。
3. 表单视图控制器:UI层面的交互中介
在Odoo的前端JS框架(Owl)中,每个表单视图都有一个对应的 FormController
。它充当了页面上所有UI组件(我们称之为Widget,现在更多是Component)的中介。
经典案例:@api.onchange
当你在表单上改变一个字段的值时(比如,在销售订单行上选择一个产品),会发生什么?
- 同事对象 (Colleagues):
product_id
字段的组件,name
(描述)字段的组件,price_unit
(单价)字段的组件等。 - 中介者 (Mediator):
FormController
和后端的onchange
机制。
交互流程:
- 用户在
product_id
组件中选择了一个产品。 - 该组件通知
FormController
(中介者)它的值发生了变化。 FormController
(中介者)并不会直接去告诉name
组件或price_unit
组件去更新。相反,它会收集当前表单上所有字段的值,通过RPC调用后端的onchange
方法。- 后端的
onchange
方法(如_onchange_product_id
)执行业务逻辑,计算出需要更新的字段(如name
、price_unit
)的新值,并将其返回。 FormController
(中介者)接收到返回的更新数据后,再去通知name
组件和price_unit
组件更新它们自己的显示值。
在这个流程中,product_id
组件无需知道它需要影响哪些其他组件。它只需向中介者报告自己的变化。所有的协调和数据流转都由中介者统一处理,使得UI组件之间高度解耦。
四、中介者模式的优势与风险
优势
- 降低耦合度 (Decoupling): 将网状依赖变为星状依赖,同事对象之间不再有直接联系,维护和修改变得更加容易。
- 集中控制逻辑 (Centralized Control): 复杂的交互逻辑被封装在中介者内部,使得逻辑更加清晰,易于理解和管理。
- 提高复用性 (Increased Reusability): 同事对象因为不依赖其他同事,所以更容易被复用。一个
sale.order.line
模型理论上可以被用在不同的中介者下(比如用在采购订单行上,如果设计允许的话)。
潜在风险
- 中介者膨胀 (Mediator Bloat / God Object): 最大的风险是中介者自身可能变得异常复杂,承担了过多的责任,成为一个难以维护的“上帝对象”。在Odoo中,一个拥有几千行代码的父模型或向导就是这种风险的体现。
- 应对策略: 设计时要有意识地保持中介者的职责单一,只负责“协调”。如果某段业务逻辑本身很复杂,应该将其封装在对应的同事对象的方法中,中介者只负责在合适的时机调用它,而不是自己实现所有细节。
结论
中介者模式是Odoo架构设计的基石之一。它并非以一个具体的类存在,而是作为一种核心思想,体现在父子模型的关系、向导的设计以及前端控制层中。
理解Odoo如何运用中介者模式,可以帮助我们:
- 看懂Odoo核心模块复杂业务背后的清晰结构。
- 在自己的定制开发中,自觉地利用父模型或向导来充当协调者,避免写出混乱、高耦合的代码。
- 在面对复杂的交互需求时,优先思考“是否需要一个中介者?”,而不是让对象之间直接对话。
基于odoo17的设计模式详解---中介模式