基于odoo17的设计模式详解---工厂模式
大家好,我是你的Odoo技术伙伴。在Odoo开发中,我们几乎每天都在创建各种对象:新的客户记录、销售订单、发票、库存移动等等。虽然表面上我们只是简单地调用self.env['some.model'].create(...)
,但在这看似简单的操作背后,Odoo的ORM扮演着一个极其强大和复杂的工厂(Factory) 角色。今天,我们将深入探讨经典的工厂模式(Factory Pattern),并揭示Odoo是如何通过其核心架构,将工厂模式的思想运用到极致,从而实现了对象创建的解耦、灵活性和可扩展性。
一、什么是工厂模式?
让我们从一个简单的比喻开始:去一家车行买车。
你走进一家大型汽车品牌的展厅,告诉销售顾问:“我想要一辆红色的、带天窗的SUV。”
- 你(客户端 Client):提出了一个需求(创建一辆特定的车)。
- 销售顾问和其背后的生产系统(工厂 Factory):接收你的需求。
- 汽车(产品 Product):最终生产出的那辆SUV。
你不需要关心这辆SUV是在哪个城市的哪个工厂生产的,它的底盘、发动机、车漆是如何一步步组装起来的。你只需要向“工厂”(车行)下达指令,它就会负责所有复杂的创建细节,并最终交付给你一个符合要求的产品。如果这家车行还卖轿车和卡车,你同样可以用类似的方式下单,工厂会根据你的需求,调用不同的生产线来制造。
转换成软件设计的语言:
工厂模式是一种创建型设计模式,它提供一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。其核心思想是:将对象的实例化过程封装在一个专门的“工厂”中,由工厂来决定具体创建哪个类的实例。
二、Odoo ORM:终极的对象创建工厂
在Odoo中,最核心、最强大的工厂就是Odoo的环境和ORM本身。当你写下self.env['res.partner']
时,你得到的并不是一个ResPartner
类,而是一个与该模型关联的、功能强大的模型代理对象。这个代理对象,就是我们与ORM工厂交互的入口。
1. 简单工厂模式的体现:self.env[model_name]
self.env
这个“超级工厂”可以根据你提供的“产品型号”(模型名称字符串),为你生产出对应的“产品操作器”(模型代理对象)。
# 向工厂请求 'res.partner' 产品的操作器
partner_factory_proxy = self.env['res.partner']# 向工厂请求 'sale.order' 产品的操作器
order_factory_proxy = self.env['sale.order']# 使用操作器来创建具体的产品实例 (记录)
new_partner = partner_factory_proxy.create({'name': 'Factory-made Partner'})
这里,self.env
扮演了简单工厂的角色。客户端(你的代码)通过一个字符串参数,来决定需要哪个“生产线”,而无需直接导入和实例化ResPartner
或SaleOrder
这些具体的模型类。这实现了客户端与具体模型类的解耦。
2. 工厂方法模式的体现:可被子类重写的创建逻辑
工厂方法模式的核心是:定义一个用于创建对象的接口,但让子类决定实例化哪个类。在Odoo中,这个思想通过重写create()
方法来完美实现。
create()
方法就是那个“创建对象的接口”。每个模型都可以通过继承来重写它,从而改变或增强其创建过程。
场景:创建一个销售订单时,自动生成一个唯一的、带前缀的序列号。Odoo的sale.order
模型就是这么做的。
# addons/sale/models/sale_order.py
from odoo import models, fields, apiclass SaleOrder(models.Model):_inherit = 'sale.order'name = fields.Char(..., copy=False, default='/')@api.model_create_multidef create(self, vals_list):# --- 这是被重写的工厂方法 ---for vals in vals_list:# 1. 在创建前,执行特殊的逻辑if 'name' not in vals or vals.get('name') == '/':# 向序列号子系统(另一个工厂)请求一个新序列号vals['name'] = self.env['ir.sequence'].next_by_code('sale.order') or '/'# 2. 调用父类的工厂方法,完成基础的对象创建orders = super(SaleOrder, self).create(vals_list)# 3. 在创建后,执行其他逻辑...return orders
代码解读:
create()
是工厂方法:它负责创建sale.order
的实例。- 子类决定实现:
SaleOrder
类(作为models.Model
的子类)重写了create
方法,加入了自己独特的实例化逻辑(生成序列号)。 - 客户端无感:调用
self.env['sale.order'].create(...)
的代码,完全不知道背后还有生成序列号这么复杂的过程。它只是向工厂下达了指令,工厂则按照SaleOrder
这条“特殊生产线”的规定,完成了产品的创建。 - 扩展性:任何一个模块都可以通过
_inherit
再次重写create
方法,进一步定制这条生产线,而不会影响到客户端代码。
3. 抽象工厂模式的影子:提供一系列相关的对象
抽象工厂模式旨在提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。在Odoo中,一些复杂的业务方法也扮演了抽象工厂的角色。
场景:确认销售订单时,需要同时创建提货单和库存移动。
sale.order
的action_confirm()
方法(一个外观方法)内部,调用了_create_stock_moves()
等方法。这个过程就像一个“物流单据抽象工厂”。
- 抽象工厂接口:“为销售订单准备物流”这个业务概念。
- 具体工厂:
action_confirm
的实现。 - 抽象产品:“物流单据”。
- 具体产品:
stock.picking
(提货单)实例和stock.move
(库存移动)实例。
# 伪代码,简化自 sale.order
def action_confirm(self):# ...# 调用一个“工厂方法”来创建提货单pickings = self._create_pickings() # 另一个“工厂方法”来创建库存移动,并关联到提货单moves = self.order_line._create_stock_moves(pickings)# ...
客户端只需要调用order.action_confirm()
,这个“抽象工厂”就会负责创建出一整套相互关联的物流对象(提货单和其下的库存移动),而客户端无需关心stock.picking
和stock.move
的创建细节和它们之间的关联逻辑。
四、优势与适用场景
优势
- 高度解耦:将对象的创建逻辑与使用逻辑分离。客户端只依赖于工厂和产品接口,不依赖于具体的产品实现。
- 单一职责:创建对象的复杂逻辑被集中到工厂中,使得代码结构更清晰。
- 易于扩展:增加一个新的产品类型,只需要创建一个新的产品类,并在工厂中增加一个相应的创建分支即可,无需修改客户端代码,符合开闭原则。在Odoo中,就是通过创建一个新模块来继承和重写
create
方法。 - 集中控制:可以在工厂方法中加入权限检查、日志记录、资源池管理等统一的控制逻辑。
结论
工厂模式是Odoo框架设计的基石。它并非以某个僵硬的Factory
基类形式存在,而是一种内化于ORM核心、贯穿于模块化开发中的设计哲学。
self.env
是一个强大的简单工厂,负责按需提供模型操作的入口。- 可被重写的
create()
方法 是工厂方法模式的完美体现,它赋予了每个模型定制自身创建过程的能力。 - 复杂的业务方法 则常常扮演抽象工厂的角色,负责创建一系列相互关联的对象。
理解Odoo中的工厂模式,意味着你能够洞察Odoo是如何在保证灵活性的同时,管理着成百上千个模型的实例化过程。它将帮助你编写出更符合Odoo架构、更具扩展性和维护性的代码,让你从一个简单的“对象使用者”,成长为一个懂得如何“设计对象生产线”的架构师。