基于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.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;每個狀態都反映了進程在內核調度、資源等待或父…