【設計模式】訪問者模式模式

訪問者模式(Visitor Pattern)詳解


一、訪問者模式簡介

訪問者模式(Visitor Pattern) 是一種 行為型設計模式(對象行為型模式),它允許你在不修改對象結構的前提下,為對象結構中的元素添加新的操作。

你可以這樣理解:

“有一個公司組織結構圖(包含多個部門、員工),現在你想分別計算工資總額、打印員工名單、生成報表等不同功能。如果每次都要修改每個類來支持新功能,會非常麻煩。而訪問者模式就像請來不同的‘專家’(訪問者)——一個財務專家算工資,一個人事專家做花名冊——他們各自去‘訪問’每個員工并完成自己的任務。”

核心思想是:將數據結構與作用于結構上的操作分離

它為操作存儲不同類型元素的對象結構提供了一種解決方案。
用戶可以對不同類型的元素施加不同的操作。

訪問者模式包含以下5個角色
Visitor(抽象訪問者)
ConcreteVisitor(具體訪問者)
Element(抽象元素)
ConcreteElement(具體元素)
ObjectStructure(對象結構)

在這里插入圖片描述


二、解決的問題類型

訪問者模式主要用于解決以下問題:

  • 需要對一個復雜的對象結構(如樹、列表)執行多種不同的操作,且這些操作可能會頻繁增加
  • 不想修改現有類來添加新功能(避免破壞封裝性或違反開閉原則)。
  • 希望將相關操作集中在一個類中(即訪問者),而不是分散在各個數據類中

三、使用場景

場景示例
編譯器設計抽象語法樹(AST)的解析、類型檢查、代碼生成等
文檔處理系統對文檔中的段落、圖片、表格進行渲染、導出PDF、統計字數等
UI組件樹操作遍歷組件樹進行布局、繪制、事件分發等
報表生成對一組對象進行匯總、分析、生成統計報告

一個對象結構包含多個類型的對象,希望對這些對象實施一些依賴其具體類型的操作。
需要對一個對象結構中的對象進行很多不同的且不相關的操作,并需要避免讓這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類。
對象結構中對象對應的類很少改變,但經常需要在此對象結構上定義新的操作。


四、核心概念

  1. Visitor(訪問者接口):定義對每種元素的訪問方法,如 visit(ElementA)visit(ElementB)
  2. ConcreteVisitor(具體訪問者):實現訪問者接口,完成具體操作(如打印、計算、導出等)。
  3. Element(元素接口):定義 accept(Visitor) 方法,用于接收訪問者。
  4. ConcreteElement(具體元素):實現 accept 方法,調用訪問者的對應 visit 方法。
  5. ObjectStructure(對象結構):如集合、樹等,能枚舉元素并讓訪問者遍歷它們。

五、實際代碼案例(Java)

我們以一個“文檔編輯器”為例,文檔包含文本段落和圖片,我們需要實現“渲染”和“導出為HTML”兩種操作。

1. 定義訪問者接口

// 訪問者接口
interface DocumentVisitor {void visit(Paragraph paragraph);void visit(Image image);
}

2. 定義元素接口

// 元素接口
interface DocumentElement {void accept(DocumentVisitor visitor);
}

3. 創建具體元素類

// 段落元素
class Paragraph implements DocumentElement {private String content;public Paragraph(String content) {this.content = content;}public String getContent() {return content;}@Overridepublic void accept(DocumentVisitor visitor) {visitor.visit(this); // 反向調用訪問者,傳入自己}
}// 圖片元素
class Image implements DocumentElement {private String url;public Image(String url) {this.url = url;}public String getUrl() {return url;}@Overridepublic void accept(DocumentVisitor visitor) {visitor.visit(this);}
}

4. 創建具體訪問者類

// 渲染訪問者
class RenderVisitor implements DocumentVisitor {@Overridepublic void visit(Paragraph paragraph) {System.out.println("🖥? 渲染段落: " + paragraph.getContent());}@Overridepublic void visit(Image image) {System.out.println("🖼? 渲染圖片: [顯示圖片 " + image.getUrl() + "]");}
}// 導出為HTML訪問者
class HtmlExportVisitor implements DocumentVisitor {private StringBuilder html = new StringBuilder();@Overridepublic void visit(Paragraph paragraph) {html.append("<p>").append(paragraph.getContent()).append("</p>\n");}@Overridepublic void visit(Image image) {html.append("<img src=\"").append(image.getUrl()).append("\" />\n");}public String getHtml() {return html.toString();}
}

5. 創建對象結構(文檔)

import java.util.ArrayList;
import java.util.List;// 文檔結構(對象結構)
class Document {private List<DocumentElement> elements = new ArrayList<>();public void addElement(DocumentElement element) {elements.add(element);}// 接受訪問者遍歷所有元素public void accept(DocumentVisitor visitor) {for (DocumentElement element : elements) {element.accept(visitor);}}
}

6. 客戶端測試類

public class Client {public static void main(String[] args) {Document doc = new Document();doc.addElement(new Paragraph("歡迎使用我們的文檔系統。"));doc.addElement(new Image("https://example.com/logo.png"));doc.addElement(new Paragraph("這是一個示例文檔。"));System.out.println("=== 渲染文檔 ===");RenderVisitor renderVisitor = new RenderVisitor();doc.accept(renderVisitor);System.out.println("\n=== 導出為HTML ===");HtmlExportVisitor htmlVisitor = new HtmlExportVisitor();doc.accept(htmlVisitor);System.out.println(htmlVisitor.getHtml());}
}

輸出結果:

=== 渲染文檔 ===
🖥? 渲染段落: 歡迎使用我們的文檔系統。
🖼? 渲染圖片: [顯示圖片 https://example.com/logo.png]
🖥? 渲染段落: 這是一個示例文檔。=== 導出為HTML ===
<p>歡迎使用我們的文檔系統。</p>
<img src="https://example.com/logo.png" />
<p>這是一個示例文檔。</p>

典型代碼

典型的抽象訪問者類代碼:

abstract class Visitor
{public abstract void Visit(ConcreteElementA elementA);public abstract void Visit(ConcreteElementB elementB);public void Visit(ConcreteElementC elementC){//元素ConcreteElementC的操作代碼}
}

典型的具體訪問者類代碼:

class ConcreteVisitor : Visitor
{
public override void Visit(ConcreteElementA elementA) {//元素ConcreteElementA的操作代碼}public override void Visit(ConcreteElementB elementB) {//元素ConcreteElementB的操作代碼}
}

典型的抽象元素類代碼:

interface Element
{void Accept(Visitor visitor);
}

典型的具體元素類代碼:

class ConcreteElementA : Element 
{public void Accept(Visitor visitor) {visitor.Visit(this);}public void OperationA() {//業務方法}
}

典型的對象結構代碼:

using System;
using System.Collections.Generic;
class ObjectStructure
{private List<Element> list = new List<Element>(); //定義一個集合用于存儲元素對象
//接受訪問者的訪問操作
public void Accept(Visitor visitor)
{
foreach (Object obj in list){((Element)obj).Accept(visitor); //遍歷訪問集合中的每一個元素
}
}public void AddElement(Element element){list.Add(element);}
public void RemoveElement(Element element){list.Remove(element);}
}

訪問者模式的結構與實現
雙重分派機制
(1) 調用具體元素類的Accept(Visitor visitor)方法,并將Visitor子類對象作為其參數
(2) 在具體元素類Accept(Visitor visitor)方法內部調用傳入的Visitor對象的Visit()方法,例如Visit(ConcreteElementA elementA),將當前具體元素類對象(this)作為參數,例如visitor.Visit(this)
(3) 執行Visitor對象的Visit()方法,在其中還可以調用具體元素對象的業務方法

ConcreteElementA. Accept(Visitor visitor)↓
ConcreteVisitorA. Visit(ConcreteElementA elementA)<ConcreteVisitorA. Visit(this)>↓
ConcreteElementA. OperationA()

其他案例

  1. 某公司OA系統中包含一個員工信息管理子系統,該公司員工包括正式員工和臨時工,每周人力資源部和財務部等部門需要對員工數據進行匯總,匯總數據包括員工工作時間、員工工資等。該公司基本制度如下:
    (1) 正式員工每周工作時間為40小時,不同級別、不同部門的員工每周基本工資不同;如果超過40小時,超出部分按照100元/小時作為加班費;如果少于40小時,所缺時間按照請假處理,請假所扣工資以80元/小時計算,直到基本工資扣除到零為止。除了記錄實際工作時間外,人力資源部需記錄加班時長或請假時長,作為員工平時表現的一項依據。
    (2) 臨時工每周工作時間不固定,基本工資按小時計算,不同崗位的臨時工小時工資不同。人力資源部只需記錄實際工作時間。
    人力資源部和財務部工作人員可以根據各自的需要對員工數據進行匯總處理,人力資源部負責匯總每周員工工作時間,而財務部負責計算每周員工工資。
    現使用訪問者模式設計該系統,繪制類圖。

在這里插入圖片描述

  1. 購物車
    顧客在超市中將選擇的商品,如蘋果、圖書等放在購物車中,然后到收銀員處付款。在購物過程中,顧客需要對這些商品進行訪問,以便確認這些商品的質量,之后收銀員計算價格時也需要訪問購物車內顧客所選擇的商品。此時,購物車作為一個ObjectStructure(對象結構)用于存儲各種類型的商品,而顧客和收銀員作為訪問這些商品的訪問者,他們需要對商品進行檢查和計價。不同類型的商品其訪問形式也可能不同,如蘋果需要過秤之后再計價,而圖書不需要。使用訪問者模式來設計該購物過程。

在這里插入圖片描述

  1. 獎勵審批系統
    某高校獎勵審批系統可以實現教師獎勵和學生獎勵的審批(AwardCheck),如果教師發表論文數超過10篇或者學生論文超過2篇可以評選科研獎,如果教師教學反饋分大于等于90分或者學生平均成績大于等于90分可以評選成績優秀獎,使用訪問者模式設計該系統,以判斷候選人集合中的教師或學生是否符合某種獲獎要求。
    在這里插入圖片描述

設計結構
在這里插入圖片描述


六、優缺點分析

優點描述
? 符合開閉原則新增操作(訪問者)無需修改現有元素類
? 職責分離將相關操作集中到訪問者類中,提高內聚性
? 便于擴展新操作添加新功能只需新增一個訪問者類
缺點描述
? 增加新元素類困難每新增一個元素類型,所有訪問者都要修改接口并實現新方法(違反開閉原則)
? 破壞封裝性訪問者可能需要訪問元素的內部狀態
? 代碼復雜度高引入較多類和雙向調用,理解成本較高
? 性能開銷多態調用和遞歸可能導致性能下降

七、與其它模式對比

模式與訪問者模式的區別
策略模式策略是替換算法,訪問者是擴展操作
觀察者模式觀察者是事件通知,訪問者是主動遍歷
迭代器模式迭代器用于遍歷,訪問者用于操作+遍歷

八、最終小結

訪問者模式是一種強大但使用場景有限的設計模式,它特別適合那些數據結構相對穩定,但需要在其上執行多種不同操作的系統。

作為一名 Java 開發工程師,你可能會在以下場景中遇到它:

  • 編譯器、解釋器等語言處理工具;
  • 復雜的數據模型需要多種展示或處理方式;
  • 報表、導出、統計等橫切功能較多的系統。

但也要注意:如果元素類型經常變化,訪問者模式會變得難以維護。因此,它更適合“操作多、結構穩”的場景。


📌 一句話總結:

訪問者模式就像“外聘專家團隊”,他們帶著各自的工具(操作),去訪問公司里的各個部門(元素),完成專業任務,而無需改變公司原有結構。


? 建議使用時機:

  • 對象結構穩定,但操作頻繁擴展;
  • 多種操作需要集中管理;
  • 愿意接受一定的代碼復雜度換取靈活性。

以上內部部分由AI大模型生成,注意識別!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92848.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92848.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92848.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

比特幣現貨和比特幣合約的區別與聯系

一、基本定義項目現貨&#xff08;Spot&#xff09;合約&#xff08;Futures / Perpetual&#xff09;本質直接買賣比特幣本身買賣比特幣價格的衍生品合約所得資產真實的 BTC合約頭寸&#xff08;沒有直接持有 BTC&#xff09;結算方式交割比特幣現金結算&#xff08;多數平臺&…

Qt/C++開發監控GB28181系統/實時監測設備在線離線/視頻預覽自動重連/重新點播取流/低延遲

一、前言說明 一個好的視頻監控系統&#xff0c;設備掉線后能夠自動重連&#xff0c;也是一個重要的功能指標&#xff0c;如果監控系統只是個rtsp流地址&#xff0c;那非常好辦&#xff0c;只需要重新打開流地址即可&#xff0c;而gb28181中就變得復雜了很多&#xff0c;需要多…

此芯p1開發板使用OpenHarmony時llama.cpp不同優化速度對比(GPU vs CPU)

硬件環境 Cix P1 SoC 瑞莎星睿 O6 開發板 rx580顯卡 產品介紹&#xff1a; https://docs.radxa.com/orion/o6/getting-started/introduction OpenHarmony 5.0.0 使用vulkan后端的llama.cpp &#xff08;GPU&#xff09; # ./llama-bench -m /data/qwen1_5-0_5b-chat-q2_k.…

Android 四大布局:使用方式與性能優化原理

一、四大布局基本用法與特點1. LinearLayout&#xff08;線性布局&#xff09;使用方式&#xff1a;<LinearLayoutandroid:orientation"vertical" <!-- 排列方向&#xff1a;vertical/horizontal -->android:layout_width"match_parent"android:…

Redis的BigKey問題

Redis的BigKey問題 什么是大Key問題&#xff1f; 大key問題其實可以說是大value問題&#xff0c;就是某個key對應的value所占據的存儲空間太大了&#xff0c;所以導致我們在操作這個key的時候花費的時間過長&#xff08;序列化\反序列化&#xff09;&#xff0c;從而降低了redi…

TDengine IDMP 產品基本概念

基本概念 元素 (Element) IDMP 通過樹狀層次結構來組織數據&#xff0c;樹狀結構里的每個節點被稱之為元素 (Element)。元素是一個物理的或邏輯的實體。它可以是具體的物理設備&#xff08;比如一臺汽車&#xff09;&#xff0c;物理設備的一個子系統&#xff08;比如一臺汽車的…

專題二_滑動窗口_將x減到0的最小操作數

一&#xff1a;題目解釋&#xff1a;每次只能移除數組的邊界&#xff0c;移除的邊界的總和為x&#xff0c;要求返回你移除邊界的最小操作數&#xff01;也就是說你最少花幾次移除邊界&#xff0c;就能夠讓這些移除的邊界的和為x&#xff0c;則返回這個次數&#xff01;所以這個…

CentOS 7 下通過 Anaconda3 運行llm大模型、deepseek大模型的完整指南

CentOS 7 下通過 Anaconda3 運行llm大模型、deepseek大模型的完整指南A1 CentOS 7 下通過 Anaconda3 運行大模型的完整指南一、環境準備二、創建專用環境三、模型部署與運行四、優化配置常見問題解決B1 CentOS 7 下通過 Anaconda3 使用 CPU 運行 DeepSeek 大模型的完整方案一、…

Flutter應用在Windows 8上正常運行

要讓Flutter應用在Windows 8上正常運行,需滿足以下前提條件,涵蓋系統環境、依賴配置、編譯設置等關鍵環節: 一、系統環境基礎要求 Windows 8版本 必須是 Windows 8.1(核心支持),不支持早期Windows 8(需升級到8.1,微軟已停止對原版Windows 8的支持)。 確認系統版本:右…

Redis實現消息隊列三種方式

參考 Redis隊列詳解&#xff08;springboot實戰&#xff09;_redis 隊列-CSDN博客 前言 MQ消息隊列有很多種&#xff0c;比如RabbitMQ,RocketMQ,Kafka等&#xff0c;但是也可以基于redis來實現&#xff0c;可以降低系統的維護成本和實現復雜度&#xff0c;本篇介紹redis中實現…

【C++動態版本號生成方案:實現類似C# 1.0.* 的自動構建號】

C動態版本號生成方案&#xff1a;實現類似C# 1.0.* 的自動構建號 在C#中&#xff0c;1.0.*版本號格式會在編譯時自動生成構建號和修訂號。本文將介紹如何在C項目中實現類似功能&#xff0c;通過MSBuild自動化生成基于編譯時間的版本號。 實現原理 版本號構成&#xff1a;主版本…

【算法題】:斐波那契數列

用 JavaScript 實現一個 fibonacci 函數&#xff0c;滿足&#xff1a; 輸入 n&#xff08;從0開始計數&#xff09;輸出第 n 個斐波那契數&#xff08;斐波那契數列從 1 開始&#xff1a;1,1,2,3,5,8,13,21…&#xff09; 示例&#xff1a; fibonacci(0) > 1fibonacci(4) &g…

【YOLOv13[基礎]】熱力圖可視化實踐 | 腳本升級 | 優化可視化效果 | 論文必備 | GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM等

本文將進行添加YOLOv13版本的升級版熱力圖可視化功能的實踐,支持圖像熱力圖可視化、優化可視化效果、 可以選擇使用GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM, HiResCAM, LayerCAM, RandomCAM, EigenGradCAM。一個參數即可設置是否顯示檢測框等。 原圖 結果圖

ElasticSearch相關術語介紹

1.RESTful風格程序REST(英文全稱為:"Representational State Transfer")指的是一組架構約束條件和原則。它是一種軟件架構風格&#xff08;約束條件和原則的集合&#xff0c;但并不是標準&#xff09;。 REST通過資源的角度觀察網絡&#xff0c;以URI對網絡資源進行…

《從零構建大語言模型》學習筆記4,注意力機制1

《從零構建大語言模型》學習筆記4&#xff0c;自注意力機制1 文章目錄《從零構建大語言模型》學習筆記4&#xff0c;自注意力機制1前言一、實現一個簡單的無訓練權重的自注意力機制二、實現具有可訓練權重的自注意力機制1. 分步計算注意力權重2.實現自注意力Python類三、將單頭…

昇思+昇騰開發板+DeepSeek模型推理和性能優化

昇思昇騰開發板DeepSeek模型推理和性能優化 模型推理 流程&#xff1a; 權重加載 -> 啟動推理 -> 效果比較與調優 -> 性能測試 -> 性能優化 權重加載 如微調章節介紹&#xff0c;最終的模型包含兩部分&#xff1a;base model 和 LoRA adapter&#xff0c;其中base …

未給任務“Fody.WeavingTask”的必需參數“IntermediateDir”賦值。 WpfTreeView

c#專欄記錄&#xff1a; 報錯 未給任務“Fody.WeavingTask”的必需參數“IntermediateDir”賦值。 WpfTreeView 生成 解決辦法 清理和重新生成項目 完成上述配置后&#xff0c;嘗試執行以下步驟&#xff1a; 清理項目&#xff1a;刪除 bin 和 obj 文件夾。 重新生成項目&…

[Linux]學習筆記系列 -- [arm][lib]

文章目錄arch/arm/lib/delay.cregister_current_timer_delay 注冊當前定時器延遲read_current_timer 讀取當前定時器drivers/clocksource/timer-stm32.cstm32_clocksource_init STM32 平臺上初始化時鐘源https://github.com/wdfk-prog/linux-study arch/arm/lib/delay.c regis…

harbor倉庫搭建(配置https)

目錄 1. 環境準備 2. 配置https的原因 3. 生成ca證書 4. 搭建harbor倉庫 5. 訪問harbor 6. 修改加密算法 1. 環境準備 需要提前安裝docker和docker-compose&#xff0c;harbor倉庫版本越新&#xff0c;對應的docker和docker-compose版本越新。 主機IP192.168.48.19dock…

C++多線程服務器

C多線程服務器 因為自己同時在看多本書&#xff0c;之前看過《TCP/IP 網絡編程》一書&#xff0c;其中有一個自己編寫一個多線程服務器的例子&#xff0c;于是就把代碼直接抄了一變。 在學習網絡編程前需要先了解網絡的7層模型。 具體代碼如下&#xff1a; 服務器端&#xff1a…