設計模式(十三)結構型:代理模式詳解
代理模式(Proxy Pattern)是 GoF 23 種設計模式中的結構型模式之一,其核心價值在于為其他對象提供一種間接訪問的機制,以控制對原始對象的訪問。它通過引入一個“代理”對象,作為客戶端與真實對象之間的中介,從而在不改變原始接口的前提下,實現訪問控制、延遲初始化、日志記錄、權限校驗、緩存、遠程通信等附加功能。代理模式是實現“開閉原則”和“單一職責原則”的重要手段,廣泛應用于遠程服務調用(RMI、Web Service)、虛擬代理(延遲加載)、保護代理(權限控制)、智能引用(資源管理)等場景,是構建安全、高效、可維護系統的關鍵架構模式。
一、詳細介紹
代理模式解決的是“直接訪問目標對象存在限制或需要增強控制”的問題。在某些情況下,客戶端不能或不應直接訪問真實對象,例如:
- 對象創建代價高昂(如大型圖像、數據庫連接),需延遲加載;
- 對象位于遠程主機,需通過網絡訪問;
- 對象涉及敏感操作,需進行權限驗證;
- 需要監控對象的訪問行為(如調用次數、執行時間)。
代理模式通過一個與真實對象具有相同接口的代理對象,攔截所有對真實對象的請求,并在轉發前或后執行額外邏輯。客戶端通過代理與真實對象交互,整個過程對客戶端透明。
該模式包含以下核心角色:
- Subject(抽象主題):定義真實對象和代理對象的公共接口,客戶端通過該接口訪問目標。可以是接口或抽象類。
- RealSubject(真實主題):實現
Subject
接口,是代理所代表的真實對象,包含核心業務邏輯。 - Proxy(代理類):實現
Subject
接口,持有對RealSubject
的引用。它控制對真實對象的訪問,可在調用前后執行額外操作(如檢查權限、緩存結果、記錄日志)。
根據使用目的不同,代理模式可分為多種類型:
- 遠程代理(Remote Proxy):為位于不同地址空間的對象提供本地代表,隱藏網絡通信細節(如 RMI、gRPC Stub)。
- 虛擬代理(Virtual Proxy):延遲創建開銷大的對象,直到真正需要時才初始化(如圖片懶加載)。
- 保護代理(Protection Proxy):控制對對象的訪問權限,根據角色決定是否允許操作(如管理員 vs 普通用戶)。
- 智能引用(Smart Reference):在訪問對象時執行額外操作,如引用計數、空指針檢查、緩存結果。
- 緩存代理(Caching Proxy):緩存真實對象的操作結果,提高性能,避免重復計算或遠程調用。
代理模式的關鍵優勢:
- 增強控制:可在訪問前后插入邏輯,實現橫切關注點。
- 解耦客戶端與真實對象:客戶端不依賴具體實現,便于替換或擴展。
- 提高安全性:通過保護代理實現權限隔離。
- 優化性能:通過虛擬代理延遲加載,緩存代理減少重復操作。
與“裝飾器模式”相比,代理關注訪問控制,裝飾器關注功能增強;代理通常不改變對象行為本質,而是控制何時、如何訪問;裝飾器則明確添加新功能。與“外觀模式”相比,代理封裝的是單個對象的訪問,外觀封裝的是多個子系統的協作。
二、代理模式的UML表示
以下是代理模式的標準 UML 類圖:
圖解說明:
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圖(簡化版)
運行說明:
ProtectedDocumentProxy
實現了Document
接口,持有文件名和用戶信息。- 真實文檔
RealDocument
在首次open()
或edit()
時才創建(虛擬代理特性)。 - 代理在調用前檢查權限(保護代理),并記錄訪問日志(智能引用)。
- 客戶端通過統一接口操作,無法感知代理的存在。
四、總結
特性 | 說明 |
---|---|
核心目的 | 控制對對象的訪問,增強安全性與靈活性 |
實現機制 | 實現相同接口,持有真實對象引用,攔截并轉發請求 |
優點 | 訪問控制、延遲加載、日志監控、遠程透明化、解耦客戶端 |
缺點 | 增加系統復雜性、可能引入性能開銷(間接調用)、需維護代理邏輯 |
適用場景 | 權限控制、遠程服務、延遲初始化、緩存、資源管理、AOP |
不適用場景 | 對象簡單、無需控制、性能極度敏感 |
代理模式使用建議:
- 代理類應盡量輕量,避免成為性能瓶頸。
- 可結合工廠模式或依賴注入創建代理。
- 在 Java 中,動態代理(
java.lang.reflect.Proxy
)可減少靜態代理的類膨脹問題。 - Spring AOP 的
JDK Dynamic Proxy
和CGLIB
是代理模式的高級應用。
架構師洞見:
代理模式是“間接性”與“控制力”的完美結合。在現代架構中,其思想已滲透到微服務治理、API 網關、服務網格(Istio/Linkerd) 和AOP(面向切面編程) 的核心。例如,在服務網格中,Sidecar 代理攔截所有進出服務的流量,實現熔斷、限流、加密;在 Spring 框架中,@Transactional
注解通過代理實現聲明式事務管理;在前端,Proxy 對象用于實現響應式數據監聽(如 Vue 3)。未來趨勢是:代理模式將與AI 安全網關結合,智能代理可動態分析請求內容并決定是否放行;在邊緣計算中,設備代理將統一管理異構設備的通信協議;在元編程和運行時增強中,代理將成為實現熱更新、動態配置的核心機制。
掌握代理模式,有助于設計出安全、可控、可監控的系統。作為架構師,應在系統邊界、服務入口、資源訪問點主動引入代理層,將“控制邏輯”與“業務邏輯”分離。代理不僅是模式,更是系統治理的哲學——它告訴我們:真正的掌控力,來自于對“訪問路徑”的精心設計與智能干預。