設計模式(十三)結構型:代理模式詳解

設計模式(十三)結構型:代理模式詳解

代理模式(Proxy Pattern)是 GoF 23 種設計模式中的結構型模式之一,其核心價值在于為其他對象提供一種間接訪問的機制,以控制對原始對象的訪問。它通過引入一個“代理”對象,作為客戶端與真實對象之間的中介,從而在不改變原始接口的前提下,實現訪問控制、延遲初始化、日志記錄、權限校驗、緩存、遠程通信等附加功能。代理模式是實現“開閉原則”和“單一職責原則”的重要手段,廣泛應用于遠程服務調用(RMI、Web Service)、虛擬代理(延遲加載)、保護代理(權限控制)、智能引用(資源管理)等場景,是構建安全、高效、可維護系統的關鍵架構模式。

一、詳細介紹

代理模式解決的是“直接訪問目標對象存在限制或需要增強控制”的問題。在某些情況下,客戶端不能或不應直接訪問真實對象,例如:

  • 對象創建代價高昂(如大型圖像、數據庫連接),需延遲加載;
  • 對象位于遠程主機,需通過網絡訪問;
  • 對象涉及敏感操作,需進行權限驗證;
  • 需要監控對象的訪問行為(如調用次數、執行時間)。

代理模式通過一個與真實對象具有相同接口的代理對象,攔截所有對真實對象的請求,并在轉發前或后執行額外邏輯。客戶端通過代理與真實對象交互,整個過程對客戶端透明。

該模式包含以下核心角色:

  • Subject(抽象主題):定義真實對象和代理對象的公共接口,客戶端通過該接口訪問目標。可以是接口或抽象類。
  • RealSubject(真實主題):實現 Subject 接口,是代理所代表的真實對象,包含核心業務邏輯。
  • Proxy(代理類):實現 Subject 接口,持有對 RealSubject 的引用。它控制對真實對象的訪問,可在調用前后執行額外操作(如檢查權限、緩存結果、記錄日志)。

根據使用目的不同,代理模式可分為多種類型:

  1. 遠程代理(Remote Proxy):為位于不同地址空間的對象提供本地代表,隱藏網絡通信細節(如 RMI、gRPC Stub)。
  2. 虛擬代理(Virtual Proxy):延遲創建開銷大的對象,直到真正需要時才初始化(如圖片懶加載)。
  3. 保護代理(Protection Proxy):控制對對象的訪問權限,根據角色決定是否允許操作(如管理員 vs 普通用戶)。
  4. 智能引用(Smart Reference):在訪問對象時執行額外操作,如引用計數、空指針檢查、緩存結果。
  5. 緩存代理(Caching Proxy):緩存真實對象的操作結果,提高性能,避免重復計算或遠程調用。

代理模式的關鍵優勢:

  • 增強控制:可在訪問前后插入邏輯,實現橫切關注點。
  • 解耦客戶端與真實對象:客戶端不依賴具體實現,便于替換或擴展。
  • 提高安全性:通過保護代理實現權限隔離。
  • 優化性能:通過虛擬代理延遲加載,緩存代理減少重復操作。

與“裝飾器模式”相比,代理關注訪問控制,裝飾器關注功能增強;代理通常不改變對象行為本質,而是控制何時、如何訪問;裝飾器則明確添加新功能。與“外觀模式”相比,代理封裝的是單個對象的訪問,外觀封裝的是多個子系統的協作

二、代理模式的UML表示

以下是代理模式的標準 UML 類圖:

implements
implements
has a
uses
?interface?
Subject
+request()
RealSubject
+request()
Proxy
-realSubject: RealSubject
+request()
+preRequest()
+postRequest()
Client
-subject: Subject
+doWork()

圖解說明

  • Subject 是統一接口,客戶端通過它與真實對象或代理交互。
  • RealSubject 是真實業務對象。
  • Proxy 持有 RealSubject 引用,在 request() 中可調用 preRequest()postRequest() 執行額外邏輯。
  • 客戶端通過 Subject 接口調用,無法區分是代理還是真實對象。

三、一個簡單的Java程序實例及其UML圖

以下是一個文檔管理系統中“受保護的文件訪問”示例,展示如何使用保護代理控制對敏感文件的訪問。

Java 程序實例
// 抽象主題:文件訪問接口
interface Document {void open();void edit();
}// 真實主題:實際文檔對象
class RealDocument implements Document {private String fileName;public RealDocument(String fileName) {this.fileName = fileName;loadFromDisk(); // 模擬耗時操作}private void loadFromDisk() {System.out.println("📄 正在從磁盤加載文檔: " + fileName);try {Thread.sleep(1000); // 模擬加載延遲} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("? 文檔 " + fileName + " 加載完成");}@Overridepublic void open() {System.out.println("🔓 打開文檔: " + fileName);}@Overridepublic void edit() {System.out.println("?? 編輯文檔: " + fileName);}
}// 代理類:保護代理,控制文檔訪問權限
class ProtectedDocumentProxy implements Document {private String fileName;private RealDocument realDocument; // 延遲初始化private String currentUser;private boolean isAdmin;public ProtectedDocumentProxy(String fileName, String currentUser, boolean isAdmin) {this.fileName = fileName;this.currentUser = currentUser;this.isAdmin = isAdmin;}@Overridepublic void open() {if (canAccess()) {// 虛擬代理:延遲加載真實對象if (realDocument == null) {realDocument = new RealDocument(fileName);}logAccess("open");realDocument.open();} else {System.out.println("? 用戶 " + currentUser + " 無權打開文檔: " + fileName);}}@Overridepublic void edit() {if (canModify()) {if (realDocument == null) {realDocument = new RealDocument(fileName);}logAccess("edit");realDocument.edit();} else {System.out.println("? 用戶 " + currentUser + " 無權編輯文檔: " + fileName);}}// 權限檢查:讀取權限private boolean canAccess() {return isAdmin || currentUser.equals("owner");}// 權限檢查:修改權限private boolean canModify() {return isAdmin; // 只有管理員可編輯}// 訪問日志private void logAccess(String operation) {System.out.println("📝 日志: 用戶 [" + currentUser + "] 執行 [" + operation + "] 操作 on " + fileName);}
}// 客戶端使用示例
public class ProxyPatternDemo {public static void main(String[] args) {System.out.println("🔐 文檔管理系統 - 保護代理示例\n");// 創建代理對象(不立即加載真實文檔)Document doc = new ProtectedDocumentProxy("財務報告.docx", "alice", false);// 普通用戶嘗試打開文檔System.out.println("👉 用戶 alice (普通用戶) 嘗試打開文檔:");doc.open(); // 允許打開System.out.println("\n👉 用戶 alice 嘗試編輯文檔:");doc.edit(); // 拒絕編輯System.out.println("\n" + "=".repeat(50) + "\n");// 管理員訪問Document adminDoc = new ProtectedDocumentProxy("財務報告.docx", "admin", true);System.out.println("👉 用戶 admin (管理員) 嘗試打開并編輯文檔:");adminDoc.open();adminDoc.edit();System.out.println("\n💡 說明:真實文檔僅在首次訪問時加載,且權限由代理控制。");}
}
實例對應的UML圖(簡化版)
implements
implements
creates on demand
uses
?interface?
Document
+open()
+edit()
RealDocument
-fileName: String
+RealDocument(fileName: String)
+open()
+edit()
ProtectedDocumentProxy
-fileName: String
-realDocument: RealDocument
-currentUser: String
-isAdmin: boolean
+open()
+edit()
+canAccess()
+canModify()
+logAccess(operation: String)
Client
+main(args: String[])

運行說明

  • ProtectedDocumentProxy 實現了 Document 接口,持有文件名和用戶信息。
  • 真實文檔 RealDocument 在首次 open()edit() 時才創建(虛擬代理特性)。
  • 代理在調用前檢查權限(保護代理),并記錄訪問日志(智能引用)。
  • 客戶端通過統一接口操作,無法感知代理的存在。

四、總結

特性說明
核心目的控制對對象的訪問,增強安全性與靈活性
實現機制實現相同接口,持有真實對象引用,攔截并轉發請求
優點訪問控制、延遲加載、日志監控、遠程透明化、解耦客戶端
缺點增加系統復雜性、可能引入性能開銷(間接調用)、需維護代理邏輯
適用場景權限控制、遠程服務、延遲初始化、緩存、資源管理、AOP
不適用場景對象簡單、無需控制、性能極度敏感

代理模式使用建議

  • 代理類應盡量輕量,避免成為性能瓶頸。
  • 可結合工廠模式或依賴注入創建代理。
  • 在 Java 中,動態代理(java.lang.reflect.Proxy)可減少靜態代理的類膨脹問題。
  • Spring AOP 的 JDK Dynamic ProxyCGLIB 是代理模式的高級應用。

架構師洞見:
代理模式是“間接性”與“控制力”的完美結合。在現代架構中,其思想已滲透到微服務治理API 網關服務網格(Istio/Linkerd)AOP(面向切面編程) 的核心。例如,在服務網格中,Sidecar 代理攔截所有進出服務的流量,實現熔斷、限流、加密;在 Spring 框架中,@Transactional 注解通過代理實現聲明式事務管理;在前端,Proxy 對象用于實現響應式數據監聽(如 Vue 3)。

未來趨勢是:代理模式將與AI 安全網關結合,智能代理可動態分析請求內容并決定是否放行;在邊緣計算中,設備代理將統一管理異構設備的通信協議;在元編程運行時增強中,代理將成為實現熱更新、動態配置的核心機制。

掌握代理模式,有助于設計出安全、可控、可監控的系統。作為架構師,應在系統邊界、服務入口、資源訪問點主動引入代理層,將“控制邏輯”與“業務邏輯”分離。代理不僅是模式,更是系統治理的哲學——它告訴我們:真正的掌控力,來自于對“訪問路徑”的精心設計與智能干預。

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

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

相關文章

24點數學游戲(窮舉法求解表達式)

摘要本畢業設計旨在利用MATLAB技術實現一個24點數學游戲,采用窮舉法求解所有可能的表達式組合。通過全排列數字、枚舉運算符及括號位置,結合遞歸回溯算法,系統能夠高效地搜索所有可能的運算路徑,并驗證結果是否為24。實驗結果表明…

【web應用】如何進行前后端調試Debug? + 前端JavaScript調試Debug?

文章目錄一、前后端:后端以Debug模式運行后端項目,打斷點二、前后端:前端項目在瀏覽器中調試三、單獨前端:前端JavaScript調試1、控制臺輸出2、網頁調試器中添加斷點3、debugger關鍵字一、前后端:后端以Debug模式運行后…

FreeCAD開發樓梯參數化三維模型和鋼格柵

根據樓梯標準圖集開發各種樓梯。上行左轉,上行右轉,對應的欄桿也是配套2種。樓梯總成鋼格柵標準里的跨度和承載 扁鋼尺寸,輕松切換和修改參數。格柵綜合本來格柵上橫桿是冷軋扭鋼筋,先繪制一個圓柱,再做一個內切正方形…

【AcWing 836題解】合并集合

AcWing 836. 合并集合 【題目描述】 在查看解析之前,先給自己一點時間思考哦! 【題解】 并查集是一種用于處理集合合并與查詢問題的數據結構,通常支持以下兩種操作: Find:查詢一個元素所在的集合。 Union&#xff1a…

MySQL鎖機制與MVCC原理剖析

在MySQL中,我們使用到了它的各種類鎖;按照它的維度,有各種鎖 從數據庫的操作粒度有,表鎖,行鎖。從數據庫的操作的類型,有讀鎖和寫鎖。性能上有樂觀鎖和悲觀鎖。 在上一篇文章中的事務隔離級別,需…

C++學習(線程相關)

目錄 一、線程庫thread 1.使用外部函數 2. 使用類的函數 3. 添加參數 二、線程庫 mutex 1.使用lock()方法 2.try_lock()方法 三、線程庫lock_guard 四、線程庫unique_lock 1.adopt_lock 2.defer_lock() 五、線程庫call_once 六、線程庫promise & future 七、c…

EPOLLONESHOT 深度解析:Linux epoll 的單次觸發機制

EPOLLONESHOT 深度解析:Linux epoll 的單次觸發機制 EPOLLONESHOT 是 Linux epoll 接口中的高級事件標志,用于實現精確的事件單次觸發控制。以下是其全面技術解析: 核心設計理念 #mermaid-svg-Xg5sCLdddqmKsvKG {font-family:"trebuchet…

深入解析MongoDB分片原理與運維實踐指南

深入解析MongoDB分片原理與運維實踐指南 技術背景與應用場景 隨著互聯網業務的高速發展,單節點MongoDB實例在數據量和訪問并發上都面臨瓶頸。為了解決數據存儲容量受限和讀寫性能下降的問題,MongoDB官方提供了分片(Sharding)方案&…

基于Django的天氣數據可視化分析預測系統

【86-Django】基于Django的天氣數據可視化分析預測系統(完整系統源碼開發筆記詳細部署教程)? 目錄 一、項目簡介 二、項目界面展示 三、項目視頻展示 四、技術架構 五、核心功能模塊 六、部署教程一、項目簡介 隨著全球氣候變化和極端天氣事件的頻發&am…

怎么放大單片機輸出電流

單片機作為電子系統的控制核心,其 I/O 口輸出電流通常較小(一般在 10-20mA 左右),難以直接驅動繼電器、電機、大功率 LED 等需要較大工作電流的外設。因此,在實際應用中需通過特定電路放大單片機輸出電流,實…

站長百科類網站pbootcms模板(自適應手機端)+利于SEO優化(下載)

站長百科類網站pbootcms模板(自適應手機端)利于SEO優化 模板介紹: PbootCMS內核開發的模板,該模板屬于新聞資訊、新聞博客類企業使用! 頁面簡潔簡單,容易管理,附帶測試數據! 模板特點: 1、手工書…

【Golang】Go語言函數

Go語言函數 文章目錄Go語言函數Go函數特點一、函數的基本格式定義二、匿名函數三、自執行函數四、閉包函數五、延遲調用Go函數特點 無需聲明原型支持不定 變參支持多返回值支持匿名函數和閉包函數也是一種類型,一個函數可以賦值給變量不支持嵌套,一個包…

JAVA算法練習題day2

雙指針4.移動零二刷昨天的題,學習了新的數據結構StringBuilder。專為頻繁字符串拼接設計的可變字符串類。(https://blog.csdn.net/m0_73941339/article/details/145651287)二刷完昨天的題目,做到這題腦子已經轉不動了。做雙指針,一般雙指針初…

LLM2Rec-新國立-KDD2025-微調LLM獲得蘊含協同信息的embedding

文章目錄1. 背景與問題任務背景動機LLM2Rec 兩大步驟2. 方法2.1 Collaborative Supervised Fine-tuning(CSFT)2.2 Item-level Embedding Modeling2.2.1 從單向注意力 → 雙向注意力(Bidirectional attention)2.2.2 商品級別的對比…

前端學習9:JavaScript--對象與原型

前言:適合有基礎的同學入門嘗試 / 復習回憶。對象基礎:1.創建用戶對象const user {// 屬性(鍵值對)name: "小島",age: 20,isAdmin: false, }2.方法(函數屬性)sayHello() {console.log(你好&…

網絡:應用層

網絡:應用層 我們要知道,所有的問題解決都是在應用層。:happy: 協議是一種約定,也就是雙方約定好的結構化的數據。但是在讀寫數據時我們都是按字符串的方式來發送接受的,那么我們應該如和傳輸結構化的數據呢?應用層協…

rust-包和箱子

📦 圖解 Rust 代碼組織層級 #mermaid-svg-fBDy1PDZZ6bi000z {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-fBDy1PDZZ6bi000z .error-icon{fill:#552222;}#mermaid-svg-fBDy1PDZZ6bi000z .error-text{fi…

C++算法競賽篇(五)循環嵌套題型講解

C算法競賽篇(五)循環嵌套題型講解前言C循環嵌套題型講解第一題 包含數字的9第二題 求出 e 的值第三題 斐波那契數列第四題 第 n 小的質數第五題 水仙花數前言 前面的題型里我們認識了C里面的三大循環本篇博客我們開始講解C循環嵌套題型 我的個人主頁&am…

Gradio全解8——ChatInterfaceChatbot:聊天界面類與聊天機器人(3)——ChatInterface的多模態功能與附加輸入輸出

Gradio全解8——ChatInterface&Chatbot:聊天界面類與聊天機器人(3)——ChatInterface的多模態功能與附加輸入輸出8.3 ChatInterface的多模態功能與附加輸入輸出8.3.1 多模態功能1. 設置multimodal和fn參數2. 傳入MultimodalTextbox組件及…

php算法-- 關聯數組使用,優化sip賬號去重

文章目錄1 變量定義2. 核心特性code1 變量定義 類型:嵌套的關聯數組(Nested Associative Array)外層結構:[中繼ID > 賬號列表]鍵 (Key):中繼ID(字符串或整型)值 (Value):索引數組…