設計模式——訪問者設計模式(行為型)

摘要

訪問者設計模式是一種行為型設計模式,它將數據結構與作用于結構上的操作解耦,允許在不修改數據結構的前提下增加新的操作行為。該模式包含關鍵角色如元素接口、具體元素類、訪問者接口和具體訪問者類。通過訪問者模式,可以在不改變對象結構的情況下,定義新的操作行為。文章通過示例場景和類圖、時序圖等詳細介紹了訪問者設計模式的結構和實現方式,并探討了其適用場景和實戰示例。

1. 訪問者設計模式定義

將數據結構與作用于結構上的操作解耦,使得在不修改數據結構的前提下,可以增加新的操作行為。訪問者模式允許你在不改變對象結構(如樹、圖、元素集合)的前提下,定義新的操作行為,通過將這些操作封裝到獨立的 "訪問者" 對象中。

1.1. 關鍵角色說明

角色

說明

Element

定義 accept(Visitor)接口,讓訪問者訪問自己。

ConcreteElement

實現 Element接口,實現接受訪問者的具體邏輯。

Visitor

抽象訪問者,定義訪問每個元素的接口 visit(ElementA)等。

ConcreteVisitor

實現 Visitor,封裝對元素結構的具體操作,比如導出、統計、渲染等行為。

1.2. 示例場景(通俗類比)

假設你有一個對象結構為:公司組織結構,每個節點可以是 員工部門。你希望在不修改員工、部門類的前提下,分別實現:

  • 統計薪資總額
  • 導出組織結構為 HTML
  • 打印匯報關系圖

通過訪問者模式,你可以創建多個 ConcreteVisitor 來實現上述功能,而無需改動 Element 本身代碼

2. 訪問者設計模式結構

  1. 訪問者 (Visitor) 接口聲明了一系列以對象結構的具體元素為參數的訪問者方法。 如果編程語言支持重載, 這些方法的名稱可以是相同的, 但是其參數一定是不同的。
  2. 具體訪問者 (Concrete Visitor) 會為不同的具體元素類實現相同行為的幾個不同版本。
  3. 元素 (Element) 接口聲明了一個方法來 “接收” 訪問者。 該方法必須有一個參數被聲明為訪問者接口類型。
  4. 具體元素 (Concrete Element) 必須實現接收方法。 該方法的目的是根據當前元素類將其調用重定向到相應訪問者的方法。 請注意, 即使元素基類實現了該方法, 所有子類都必須對其進行重寫并調用訪問者對象中的合適方法。
  5. 客戶端 (Client) 通常會作為集合或其他復雜對象 (例如一個組合(opens new window)樹) 的代表。 客戶端通常不知曉所有的具體元素類, 因為它們會通過抽象接口與集合中的對象進行交互。

2.1. 訪問者設計模式類圖

2.2. 訪問者設計模式時序圖

3. 訪問者設計模式實現方式

訪問者設計模式的實現方式,核心在于:將作用于對象結構的操作行為封裝到獨立的訪問者類中,并通過 accept(Visitor) 方法把訪問者“注入”到元素中,從而實現對結構中不同元素的不同處理。

3.1. 步驟 1:定義元素接口 Element

public interface Element {void accept(Visitor visitor);
}

3.2. 步驟 2:實現具體元素類(ConcreteElement)

public class Employee implements Element {private String name;private double salary;public Employee(String name, double salary) {this.name = name;this.salary = salary;}// 提供訪問者訪問自己@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}// getterpublic String getName() { return name; }public double getSalary() { return salary; }
}
public class Department implements Element {private String deptName;public Department(String deptName) {this.deptName = deptName;}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}public String getDeptName() { return deptName; }
}

3.3. 步驟 3:定義訪問者接口 Visitor

public interface Visitor {void visit(Employee employee);void visit(Department department);
}

3.4. 步驟 4:實現具體訪問者(ConcreteVisitor)

public class ReportVisitor implements Visitor {@Overridepublic void visit(Employee employee) {System.out.println("員工:" + employee.getName() + ",薪資:" + employee.getSalary());}@Overridepublic void visit(Department department) {System.out.println("部門:" + department.getDeptName());}
}

3.5. 步驟 5:使用訪問者

public class Client {public static void main(String[] args) {List<Element> elements = Arrays.asList(new Employee("張三", 12000),new Employee("李四", 15000),new Department("風控部"));Visitor visitor = new ReportVisitor();for (Element element : elements) {element.accept(visitor);  // 每個元素調用 visitor.visit(this)}}
}

3.6. 說明總結

點位

描述

解耦操作和結構

不改動 Element 的結構代碼,就能添加任意多種訪問邏輯。

遵循開閉原則

新增操作時,只需新建訪問者類即可。

單一職責更清晰

每個訪問者只關注自己的行為。

缺點:擴展結構麻煩

每次新增 Element 子類,所有 Visitor都要加新的 visit() 方法。

4. 訪問者設計模式適合場景

以下是訪問者(Visitor)設計模式適合不適合使用的場景清單,便于你快速判斷在實戰開發中是否應當使用此模式。

4.1. ? 適合使用訪問者設計模式的場景

場景

說明

? 需要對對象結構中不同元素進行不同操作

如處理復雜對象樹時,不同節點類型需要不同處理(如 AST 抽象語法樹、HTML DOM、組織結構等)。

? 需要在不修改類的前提下增加新操作

元素類穩定,但經常添加新功能,適合將操作邏輯外移成訪問者類。符合開閉原則。

? 多個操作跨多個類共享處理邏輯

如統計報表、導出功能、數據校驗,每種功能可封裝為訪問者,避免元素類職責膨脹。

? 數據結構較復雜,邏輯需要分離

尤其在組合模式(Composite)中遍歷樹狀結構時,訪問者是理想搭檔。

? 需要記錄訪問軌跡/數據收集/行為鏈式執行

訪問者可收集上下文數據,實現功能鏈、審計等操作。

4.2. ? 不適合使用訪問者設計模式的場景

場景

原因

? 對象結構不穩定,經常增刪元素類型

每新增一個元素類,所有訪問者都必須修改,違反開閉原則,維護成本高。

? 操作種類少,變化不頻繁

如果只有一兩種操作,直接放到元素類中即可,訪問者反而引入了額外復雜性。

? 操作需要頻繁訪問元素內部狀態/私有字段

訪問者訪問元素的內部字段時會暴露實現細節,可能破壞封裝性。

? 數據驅動而非行為驅動系統

如果處理邏輯更多是對數據表進行規則驅動處理,不如使用策略模式、責任鏈、狀態機等。

? 系統對性能極度敏感,層層函數調用不可接受

訪問者調用鏈過長,對性能要求極高的系統中不推薦使用。

4.3. ? 實戰使用建議

建議點

內容

👍 推薦與組合模式搭配使用

樹形結構遍歷 + 多種處理邏輯,非常適合訪問者模式。

👍 可與責任鏈、模板方法組合

在訪問者中執行鏈式操作或分步驟邏輯。

?? 避免與頻繁變更的領域模型搭配

如果業務模型變化頻繁,訪問者維護成本非常高。

5. 訪問者設計模式實戰示例

以下是一個基于訪問者設計模式的 Spring 項目實戰示例,應用于金融風控場景,使用注解方式注入對象,涵蓋了完整的結構。

金融風控中,需要對不同類型的用戶(例如:個人、企業)進行多維度風險評估,比如信用評分、交易行為分析、黑名單檢查等。不同用戶類型暴露的數據結構不同,但我們希望將“風險評估邏輯”從數據結構中解耦出來。

使用訪問者模式可實現:

  • 新增風險評估邏輯(訪問者)無需修改用戶結構;
  • 用戶數據結構與評估操作解耦,符合開閉原則。

5.1. 📦 項目結構

risk-visitor
├── model
│   ├── User.java
│   ├── PersonalUser.java
│   └── CompanyUser.java
├── visitor
│   ├── RiskVisitor.java
│   ├── CreditScoreVisitor.java
│   └── FraudCheckVisitor.java
├── config
│   └── VisitorConfig.java
└── RiskEvaluationService.java

5.2. 用戶對象層(Element)

public interface User {void accept(RiskVisitor visitor);
}
@Data
public class PersonalUser implements User {private String name;private String idCard;private int creditScore;@Overridepublic void accept(RiskVisitor visitor) {visitor.visit(this);}
}
@Data
public class CompanyUser implements User {private String companyName;private String licenseNumber;private double registeredCapital;@Overridepublic void accept(RiskVisitor visitor) {visitor.visit(this);}
}

5.3. 風控訪問者接口與實現

public interface RiskVisitor {void visit(PersonalUser personalUser);void visit(CompanyUser companyUser);
}

5.3.1. 信用評分訪問者

@Component
public class CreditScoreVisitor implements RiskVisitor {@Overridepublic void visit(PersonalUser personalUser) {System.out.println("[信用評分] 用戶 " + personalUser.getName() + " 分數: " + personalUser.getCreditScore());}@Overridepublic void visit(CompanyUser companyUser) {System.out.println("[信用評分] 公司 " + companyUser.getCompanyName() + " 注冊資本: " + companyUser.getRegisteredCapital());}
}

5.3.2. 欺詐檢測訪問者

@Component
public class FraudCheckVisitor implements RiskVisitor {@Overridepublic void visit(PersonalUser personalUser) {System.out.println("[欺詐檢查] 檢查身份證是否黑名單:" + personalUser.getIdCard());}@Overridepublic void visit(CompanyUser companyUser) {System.out.println("[欺詐檢查] 檢查營業執照是否異常:" + companyUser.getLicenseNumber());}
}

5.4. 風控服務類(注解注入訪問者)

@Service
public class RiskEvaluationService {private final List<RiskVisitor> visitors;@Autowiredpublic RiskEvaluationService(List<RiskVisitor> visitors) {this.visitors = visitors;}public void evaluate(User user) {for (RiskVisitor visitor : visitors) {user.accept(visitor);}}
}

5.5. 啟動與使用

@SpringBootApplication
public class RiskApp implements CommandLineRunner {@Autowiredprivate RiskEvaluationService evaluationService;@Overridepublic void run(String... args) {PersonalUser personalUser = new PersonalUser();personalUser.setName("張三");personalUser.setIdCard("123456789");personalUser.setCreditScore(750);CompanyUser companyUser = new CompanyUser();companyUser.setCompanyName("風控科技");companyUser.setLicenseNumber("XYZ-2025");companyUser.setRegisteredCapital(5000_000);evaluationService.evaluate(personalUser);evaluationService.evaluate(companyUser);}public static void main(String[] args) {SpringApplication.run(RiskApp.class, args);}
}

5.6. ? 總結優點

  • 易于擴展新評估策略,無需改動用戶結構;
  • Spring 自動注入訪問者集合,靈活組合;
  • 清晰分離了數據結構與操作行為。

6. 訪問者設計模式思考

訪問者設計模式(Visitor Pattern)在實際開發中常常與其他設計模式組合使用,以增強系統的可擴展性、解耦能力和靈活性。下面列出訪問者模式常與哪些設計模式組合使用,以及各組合的典型應用場景和優勢

6.1. ? 訪問者模式常用組合設計模式

組合模式

組合目的/優勢

應用場景示例

組合模式(Composite)

用于遍歷和訪問復雜對象結構,訪問者可遞歸處理整個樹形結構

文檔結構、組織架構、產品分類樹等

迭代器模式(Iterator)

統一遍歷容器結構,配合訪問者實現對集合中元素的操作(如批量處理)

批量風控評估、設備監控列表操作

責任鏈模式(Chain of Responsibility)

多個訪問者對象串聯處理,解耦多個處理邏輯,每個訪問者判斷是否處理

多規則風控審批流程,每個處理節點負責一類校驗

模板方法模式(Template Method)

訪問者中封裝處理通用流程,將子類特定行為抽象為鉤子方法

通用風險評估框架,子類定義評分規則

策略模式(Strategy)

將訪問者作為策略進行注入或切換,使不同訪問行為可配置化

不同國家/行業的風險評估策略

狀態模式(State)

被訪問對象的狀態決定了訪問者邏輯流程,用于基于狀態執行不同操作

用戶行為軌跡、風控狀態遷移等

工廠模式(Factory)

訪問者工廠根據上下文動態創建訪問者對象,適配不同對象結構或執行策略

動態風控策略調度系統,按對象類型或場景創建訪問者

觀察者模式(Observer)

訪問者中執行完后通知監聽者,適用于監控、日志、審計等異步行為

風控決策日志記錄、報警事件觸發

博文參考

  • 訪問者設計模式
  • 設計模式之訪問者模式 | DESIGN

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

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

相關文章

Vue基礎(12)_Vue.js循環語句用法:列表渲染

js補充 術語解釋 循環(loop)&#xff1a;最基礎的概念, 所有重復的行為。 遞歸(recursion)&#xff1a; 在函數內調用自身, 將復雜情況逐步轉化成基本情況。 (數學)迭代(iterate) &#xff1a;在多次循環中逐步接近結果。 (編程)迭代(iterate) &#xff1a;按順序訪問線性結構中…

Linux入門(十三)動態監控系統監控網絡狀態

top與ps 命令很相似&#xff0c;它們都是用來顯示正在執行的進程&#xff0c;top與ps大的區別是top在執行一段時間可以更新正在運行的進程。 #-d 更新秒數 如果不寫-d 那默認是3秒更新 # -i 隱藏不活躍進程 top -d 5交互操作 P 按cpu使用大小排序&#xff0c;默認此項 M 按內存…

Java 中 MySQL 索引深度解析:面試核心知識點與實戰

&#x1f91f;致敬讀者 &#x1f7e9;感謝閱讀&#x1f7e6;笑口常開&#x1f7ea;生日快樂?早點睡覺 &#x1f4d8;博主相關 &#x1f7e7;博主信息&#x1f7e8;博客首頁&#x1f7eb;專欄推薦&#x1f7e5;活動信息 文章目錄 Java 中 MySQL 索引深度解析&#xff1a;面試…

Kafka集成Flume/Spark/Flink(大數據)/SpringBoot

Kafka集成Flume Flume生產者 ③、安裝Flume&#xff0c;上傳apache-flume的壓縮包.tar.gz到Linux系統的software&#xff0c;并解壓到/opt/module目錄下&#xff0c;并修改其名稱為flume Flume消費者 Kafka集成Spark 生產者 object SparkKafkaProducer{def main(args:Array[S…

debian12.9或ubuntu,vagrant離線安裝插件vagrant-libvirt,20250601

系統盤: https://mirror.lzu.edu.cn/debian-cd/12.9.0/amd64/iso-dvd/debian-12.9.0-amd64-DVD-1.iso 需要的依賴包,無需安裝ruby( sudo apt install -y ruby-full ruby-dev rubygems,后來發現不安裝會有編譯警告,還是安裝吧 ) ,無需安裝 zlib1g-dev liblzma-dev libxml2-de…

2025年軟件測試面試八股文(含答案+文檔)

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 Part1 1、你的測試職業發展是什么&#xff1f; 測試經驗越多&#xff0c;測試能力越高。所以我的職業發展是需要時間積累的&#xff0c;一步步向著高級測試工程師…

[CSS3]響應式布局

導讀 響應式就是一套代碼, 兼容大中小不同的屏幕, 即網頁內容不變, 網頁布局隨屏幕切換而改變 媒體查詢 響應式布局的核心技術是媒體查詢 媒體查詢可以檢測屏幕尺寸, 設置差異化的css 開發中的常用寫法 使用范圍屬性, 劃定屏幕范圍 max-width 最大寬度min-width 最小寬度 …

在 Windows安裝 make 的幾種方式

在 Windows 上使用 make&#xff08;通常用于自動化構建 C/C 項目等&#xff09;有幾種方法。以下是最常見的幾種安裝和使用方法&#xff1a; 文章目錄 ? 方法一&#xff1a;使用 Chocolatey 安裝 GNU Make&#xff08;推薦&#xff09;? 方法二&#xff1a;使用 WSL&#xf…

深度學習筆記25-RNN心臟病預測(Pytorch)

&#x1f368; 本文為&#x1f517;365天深度學習訓練營中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 一、前期準備 1.數據處理 import torch.nn.functional as F import numpy as np import pandas as pd import torch from torch import nn dfpd.read_csv(r&…

Pytorch知識點2

Pytorch知識點 1、官方教程2、張量&#x1f9f1; 0、數組概念&#x1f9f1; 1. 創建張量&#x1f4d0; 2. 張量形狀與維度&#x1f522; 3. 張量數據類型? 4. 張量的數學與邏輯操作&#x1f504; 5. 張量的就地操作&#x1f4e6; 6. 復制張量&#x1f680; 7. 將張量移動到加速…

池中錦鯉的自我修養,聊聊蓄水池算法

面試如泡池&#xff0c;蓄水似人生 起初你滿懷期待跳進大廠池子&#xff0c;以為自己是天選之子&#xff0c;結果發現池子里早擠滿了和你一樣的“錦鯉候選人”。HR的漁網一撒&#xff0c;撈誰全看概率——這不就是蓄水池算法的精髓嗎&#xff1f; 初入池&#xff08;i≤k&…

Linux應用開發之網絡套接字編程

套接字&#xff08;Socket&#xff09;是計算機網絡數據通信的基本概念和編程接口&#xff0c;允許不同主機上的進程&#xff08;運行中的程序&#xff09;通過網絡進行數據交換。它為應用層軟件提供了發送和接收數據的能力&#xff0c;使得開發者可以在不用深入了解底層網絡細…

小白的進階之路系列之六----人工智能從初步到精通pytorch數據集與數據加載器

本文將介紹以下內容: 數據集與數據加載器 數據遷移 如何建立神經網絡 數據集與數據加載器 處理數據樣本的代碼可能會變得混亂且難以維護;理想情況下,我們希望我們的數據集代碼與模型訓練代碼解耦,以獲得更好的可讀性和模塊化。PyTorch提供了兩個數據原語:torch.utils…

深入理解設計模式之中介者模式

深入理解設計模式之&#xff1a;中介者模式&#xff08;Mediator Pattern&#xff09; 一、什么是中介者模式&#xff1f; 中介者模式&#xff08;Mediator Pattern&#xff09;是一種行為型設計模式。它通過引入一個中介對象&#xff0c;來封裝一組對象之間的交互&#xff0…

基于通義千問的兒童陪伴學習和成長的智能應用架構。

1.整體架構概覽 我們的兒童聊天助手將采用典型的語音交互系統架構,結合大模型能力和外部知識庫: 2. 技術方案分解 2.1. 前端應用/設備 選擇: 移動App(iOS/Android)、Web應用,或者集成到智能音箱/平板等硬件設備中。技術棧: 移動App: React Native / Flutter (跨平臺…

Python Day40

Task&#xff1a; 1.彩色和灰度圖片測試和訓練的規范寫法&#xff1a;封裝在函數中 2.展平操作&#xff1a;除第一個維度batchsize外全部展平 3.dropout操作&#xff1a;訓練階段隨機丟棄神經元&#xff0c;測試階段eval模式關閉dropout 作業&#xff1a;仔細學習下測試和訓練代…

WordPress_suretriggers 權限繞過漏洞復現(CVE-2025-3102)

免責申明: 本文所描述的漏洞及其復現步驟僅供網絡安全研究與教育目的使用。任何人不得將本文提供的信息用于非法目的或未經授權的系統測試。作者不對任何由于使用本文信息而導致的直接或間接損害承擔責任。如涉及侵權,請及時與我們聯系,我們將盡快處理并刪除相關內容。 前…

基于Spring Boot 電商書城平臺系統設計與實現(源碼+文檔+部署講解)

技術范圍&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬蟲、數據可視化、小程序、安卓app、大數據、物聯網、機器學習等設計與開發。 主要內容&#xff1a;免費功能設計、開題報告、任務書、中期檢查PPT、系統功能實現、代碼編寫、論文編寫和輔導、論文…

LeetCode 39.組合總和:回溯法與剪枝優化的完美結合

一、問題本質與形式化定義 1.1 題目形式化描述 輸入&#xff1a;無重復整數數組candidates、目標值target輸出&#xff1a;所有和為target的組合集合&#xff0c;滿足&#xff1a; 元素可重復使用組合內元素非降序&#xff08;避免重復解&#xff09;解集無重復組合 1.2 問…

windows11安裝編譯QtMvvm

windows11安裝編譯QtMvvm 1 從github下載代碼2 官方的Download/Installtion3 自行構建編譯QtMvvm遇到的問題3.1 `qmake`問題執行命令報錯原因分析qmake報錯:找不到編譯器 cl解決方案3.2 `make qmake_all`問題執行命令報錯原因分析make命令未識別解決方案3.3 缺少`perl`問題執行…