零基礎設計模式——結構型模式 - 代理模式

第三部分:結構型模式 - 代理模式 (Proxy Pattern)

在學習了享元模式如何通過共享對象來優化資源使用后,我們來探討結構型模式的最后一個模式——代理模式。代理模式為另一個對象提供一個替身或占位符以控制對這個對象的訪問。

  • 核心思想:為其他對象提供一種代理以控制對這個對象的訪問。

代理模式 (Proxy Pattern)

“為其他對象提供一種代理以控制對這個對象的訪問。” (Provide a surrogate or placeholder for another object to control access to it.)

想象一下,你想看一張非常大的高清圖片,但加載它需要很長時間。或者,你可能需要訪問一個遠程服務器上的資源,網絡延遲很高。或者,你可能需要對某個操作進行權限檢查,只有特定用戶才能執行。

代理模式通過引入一個代理對象來間接訪問真實對象(也稱為主題對象或服務對象)。客戶端與代理對象交互,代理對象再根據需要與真實對象交互。

  • 真實主題 (Real Subject):實際執行任務的對象,如大圖片加載器、遠程服務接口、需要權限的操作。
  • 代理 (Proxy):控制對真實主題訪問的替身。它可以實現與真實主題相同的接口,使得客戶端可以無縫切換。

1. 目的 (Intent)

代理模式的主要目的:

  1. 控制訪問:代理可以控制客戶端對真實對象的訪問權限、時機或方式。
  2. 提供間接層:在客戶端和真實對象之間引入一個間接層,可以在這個層面上執行額外的操作,如延遲加載、緩存、日志記錄、權限驗證等。
  3. 簡化復雜性:代理可以隱藏真實對象的復雜性,例如遠程調用的網絡細節。

2. 生活中的例子 (Real-world Analogy)

  • 信用卡

    • 你的銀行賬戶(真實主題)里有錢。
    • 信用卡(代理)是你訪問銀行賬戶資金的一種方式。當你刷卡時,信用卡公司會驗證你的身份、檢查賬戶余額(控制訪問),然后才允許交易。
  • 經紀人/中介

    • 你想買賣股票(真實主題是股票交易所)。
    • 你通過股票經紀人(代理)進行操作。經紀人會處理交易的細節,你不需要直接與交易所打交道。
    • 房產中介(代理)幫助你買賣房屋(真實主題是房產本身和房主)。
  • 門禁系統

    • 大樓的某個區域(真實主題)是受限的。
    • 門禁卡或保安(代理)驗證你的身份和權限,決定是否允許你進入。
  • 明星的經紀人

    • 明星(真實主題)很忙。
    • 經紀人(代理)處理明星的日程安排、商業洽談等,過濾掉不必要的干擾,并代表明星處理事務。

3. 結構 (Structure)

代理模式通常包含以下角色:

  1. Subject (主題接口):定義了 RealSubject 和 Proxy 的共同接口。這樣,在任何使用 RealSubject 的地方都可以使用 Proxy。
  2. RealSubject (真實主題):定義了 Proxy 所代表的真實實體。這是實際執行業務邏輯的對象。
  3. Proxy (代理類)
    • 保存一個引用使得代理可以訪問實體。若 RealSubject 和 Subject 的接口相同,Proxy 會引用 Subject。
    • 提供一個與 Subject 的接口相同的接口,這樣代理就可以用來替代實體。
    • 控制對實體的存取,并可能負責創建和刪除它。
    • 其他功能依賴于代理的類型。
  4. Client (客戶端):通過 Subject 接口與 RealSubject 或 Proxy 交互。
    在這里插入圖片描述
    工作流程
  • 客戶端請求操作時,它會調用 Proxy 對象的方法。
  • Proxy 對象可能會執行一些預處理操作(如權限檢查、日志記錄)。
  • 如果需要,Proxy 會創建或獲取 RealSubject 對象的引用,并將請求委托給 RealSubject
  • RealSubject 執行實際的操作。
  • Proxy 可能會執行一些后處理操作(如結果緩存、日志記錄),然后將結果返回給客戶端。

4. 常見代理類型 (Types of Proxies)

根據代理的目的和實現方式,有幾種常見的代理類型:

  1. 虛擬代理 (Virtual Proxy)

    • 目的:延遲加載昂貴的對象。當創建真實對象的開銷很大時,虛擬代理會推遲真實對象的創建,直到客戶端真正需要它為止。
    • 例子:顯示一個包含大量圖片的文檔,圖片對象(真實主題)可以在實際滾動到屏幕上時才由虛擬代理創建和加載。
  2. 遠程代理 (Remote Proxy)

    • 目的:為位于不同地址空間(如另一臺機器上)的對象提供本地代表。遠程代理負責處理網絡通信的細節(如序列化、連接管理),使得客戶端感覺像在調用本地對象一樣。
    • 例子:Java RMI (Remote Method Invocation) 中的 Stub 對象就是遠程代理。
  3. 保護代理 (Protection Proxy)

    • 目的:控制對真實對象的訪問權限。在調用真實對象的方法之前,保護代理會檢查客戶端是否具有相應的權限。
    • 例子:根據用戶角色控制對某些敏感操作的訪問。
  4. 智能引用代理 (Smart Reference / Smart Proxy)

    • 目的:在訪問對象時執行一些額外的操作,如引用計數、加鎖以控制并發訪問、對象加載時記錄日志等。
    • 例子:C++ 中的智能指針(如 std::shared_ptr)可以看作是一種智能引用代理,負責管理對象的生命周期。
  5. 緩存代理 (Caching Proxy)

    • 目的:為開銷大的操作結果提供臨時存儲。當多個客戶端請求相同的結果時,可以直接從緩存中返回,避免重復計算或請求。
    • 例子:Web 代理服務器緩存常用網頁;應用程序緩存數據庫查詢結果。
  6. 日志代理 (Logging Proxy)

    • 目的:在方法調用前后記錄日志信息。

5. 適用場景 (When to Use)

  • 當你需要延遲初始化一個開銷很大的對象時(虛擬代理)。
  • 當你需要控制對一個對象的訪問權限時(保護代理)。
  • 當你需要為一個遠程對象提供本地代表時(遠程代理)。
  • 當你需要在訪問對象時執行一些附加操作,如日志記錄、緩存、事務管理等(智能引用代理、緩存代理、日志代理)。
  • 當你希望為一個對象提供不同級別的訪問權限時。

6. 優缺點 (Pros and Cons)

優點:

  1. 控制訪問:代理模式的核心優勢在于能夠控制對真實對象的訪問。
  2. 增強功能:可以在不修改真實對象代碼的情況下,通過代理為其增加額外的功能(如延遲加載、權限控制、日志、緩存)。
  3. 降低耦合:客戶端與真實對象解耦,客戶端只與代理接口交互。
  4. 提高性能:通過虛擬代理延遲加載或緩存代理緩存結果,可以提高系統性能。
  5. 遠程訪問透明化:遠程代理使得客戶端可以像訪問本地對象一樣訪問遠程對象。

缺點:

  1. 增加系統復雜性:引入了額外的代理類,可能會增加系統的設計和實現的復雜度。
  2. 可能引入性能開銷:由于請求需要通過代理轉發,可能會增加一次間接調用,帶來輕微的性能延遲。但通常這種開銷被代理帶來的好處(如延遲加載、緩存)所抵消或超過。
  3. 真實主題的接口依賴:代理類通常依賴于真實主題的接口,如果真實主題接口發生變化,代理類也可能需要修改。

7. 實現方式 (Implementations)

讓我們以一個虛擬代理為例,實現一個圖片加載器。真實圖片對象加載開銷大,我們希望在實際顯示時才加載它。

主題接口 (Image - Subject)
// image.go (Subject interface)
package imaging// Image 主題接口
type Image interface {Display()GetFilename() string
}
// Image.java (Subject interface)
package com.example.imaging;// 主題接口
public interface Image {void display();String getFilename();
}
真實主題 (RealImage - RealSubject)
// real_image.go (RealSubject)
package imagingimport ("fmt""time"
)// RealImage 真實主題
type RealImage struct {filename string
}func NewRealImage(filename string) *RealImage {ri := &RealImage{filename: filename}ri.loadFromDisk() // 創建時即加載return ri
}func (ri *RealImage) GetFilename() string {return ri.filename
}func (ri *RealImage) loadFromDisk() {fmt.Printf("RealImage: Loading image '%s' from disk...\n", ri.filename)// 模擬耗時操作time.Sleep(2 * time.Second)fmt.Printf("RealImage: Image '%s' loaded.\n", ri.filename)
}func (ri *RealImage) Display() {fmt.Printf("RealImage: Displaying image '%s'\n", ri.filename)
}
// RealImage.java (RealSubject)
package com.example.imaging;// 真實主題
public class RealImage implements Image {private String filename;public RealImage(String filename) {this.filename = filename;loadFromDisk(); // 創建時即加載}@Overridepublic String getFilename() {return filename;}private void loadFromDisk() {System.out.printf("RealImage: Loading image '%s' from disk...%n", filename);try {// 模擬耗時操作Thread.sleep(2000);} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("Loading interrupted for " + filename);}System.out.printf("RealImage: Image '%s' loaded.%n", filename);}@Overridepublic void display() {System.out.printf("RealImage: Displaying image '%s'%n", filename);}
}
代理類 (ProxyImage - Proxy)
// proxy_image.go (Proxy)
package imagingimport "fmt"// ProxyImage 代理類 (虛擬代理)
type ProxyImage struct {filename  stringrealImage *RealImage // 指向真實對象的指針,延遲初始化
}func NewProxyImage(filename string) *ProxyImage {fmt.Printf("ProxyImage: Created proxy for image '%s'. Real image not loaded yet.\n", filename)return &ProxyImage{filename: filename, realImage: nil}
}func (pi *ProxyImage) GetFilename() string {return pi.filename
}func (pi *ProxyImage) Display() {if pi.realImage == nil { // 延遲加載fmt.Printf("ProxyImage: Real image '%s' needs to be displayed. Loading now...\n", pi.filename)pi.realImage = NewRealImage(pi.filename)}pi.realImage.Display() // 委托給真實對象
}
// ProxyImage.java (Proxy)
package com.example.imaging;// 代理類 (虛擬代理)
public class ProxyImage implements Image {private String filename;private RealImage realImage; // 指向真實對象的引用,延遲初始化public ProxyImage(String filename) {this.filename = filename;System.out.printf("ProxyImage: Created proxy for image '%s'. Real image not loaded yet.%n", filename);}@Overridepublic String getFilename() {return filename;}@Overridepublic void display() {if (realImage == null) { // 延遲加載System.out.printf("ProxyImage: Real image '%s' needs to be displayed. Loading now...%n", filename);realImage = new RealImage(filename);}realImage.display(); // 委托給真實對象}
}
客戶端使用
// main.go (示例用法)
/*
package mainimport ("./imaging""fmt"
)func main() {fmt.Println("--- Client: Creating proxy images ---")image1 := imaging.NewProxyImage("photo1.jpg")image2 := imaging.NewProxyImage("document_scan.png")// 此時,真實圖片尚未加載fmt.Printf("\nImage 1 Filename: %s\n", image1.GetFilename())fmt.Printf("Image 2 Filename: %s\n", image2.GetFilename())fmt.Println("\n--- Client: Requesting to display image1 ---")image1.Display() // 第一次調用 Display,會觸發真實圖片加載fmt.Println("\n--- Client: Requesting to display image1 again ---")image1.Display() // 第二次調用 Display,真實圖片已加載,直接顯示fmt.Println("\n--- Client: Requesting to display image2 ---")image2.Display() // 第一次調用 Display for image2,會觸發加載
}
*/
// Main.java (示例用法)
/*
package com.example;import com.example.imaging.Image;
import com.example.imaging.ProxyImage;public class Main {public static void main(String[] args) {System.out.println("--- Client: Creating proxy images ---");Image image1 = new ProxyImage("photo1.jpg");Image image2 = new ProxyImage("document_scan.png");// 此時,真實圖片尚未加載System.out.printf("%nImage 1 Filename: %s%n", image1.getFilename());System.out.printf("Image 2 Filename: %s%n", image2.getFilename());System.out.println("%n--- Client: Requesting to display image1 ---");image1.display(); // 第一次調用 display,會觸發真實圖片加載System.out.println("%n--- Client: Requesting to display image1 again ---");image1.display(); // 第二次調用 display,真實圖片已加載,直接顯示System.out.println("%n--- Client: Requesting to display image2 ---");image2.display(); // 第一次調用 display for image2,會觸發加載}
}
*/

8. 與裝飾器模式的區別

代理模式和裝飾器模式在結構上非常相似(都包裝了另一個對象并實現了相同的接口),但它們的意圖截然不同:

  • 代理模式 (Proxy)

    • 意圖:控制對對象的訪問。代理決定客戶端是否、何時以及如何訪問真實對象。
    • 關注點:訪問控制、生命周期管理(如虛擬代理)、通信(如遠程代理)。
    • 客戶端感知:客戶端可能不知道它正在與代理交互(例如,遠程代理或保護代理對客戶端是透明的),也可能知道(例如,客戶端顯式創建一個虛擬代理)。
    • 誰創建:代理通常由系統或框架創建和管理,或者客戶端在特定場景下創建(如虛擬代理)。
  • 裝飾器模式 (Decorator)

    • 意圖:動態地向對象添加額外的職責或行為,而不改變其接口。
    • 關注點:增強對象的功能。
    • 客戶端感知:客戶端通常知道它正在使用一個裝飾過的對象,并且通常負責構建裝飾鏈。
    • 誰創建:裝飾器通常由客戶端根據需要動態地組合和應用。

簡單來說:

  • 代理:我是“替身”或“看門的”,我管著你怎么用那個真實的東西。
  • 裝飾器:我是“加料的”,我給那個真實的東西增加新花樣。

9. 總結

代理模式是一種強大的結構型模式,它通過引入一個代理對象來控制對真實對象的訪問。這種間接性使得我們可以在不修改真實對象代碼的情況下,實現諸如延遲加載、權限控制、遠程訪問、日志記錄、緩存等多種功能。根據具體需求,可以選擇不同類型的代理(虛擬代理、保護代理、遠程代理等)來解決特定的問題。

記住它的核心:提供替身,控制訪問,增強間接性

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

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

相關文章

【OSS】 前端如何直接上傳到OSS 上返回https鏈接,如果做到OSS圖片資源加密訪問

使用阿里云OSS(對象存儲服務)進行前端直接上傳并返回HTTPS鏈接,同時實現圖片資源的加密訪問,可以通過以下步驟實現: 前端直接上傳到OSS并返回HTTPS鏈接 設置OSS Bucket: 確保你的OSS Bucket已創建&#xf…

TDenigne 集群可視化管理

可視化管理工具 為方便用戶更高效地使用和管理 TDengine,TDengine 3.0 版本推出了一個全新的可視化組件 taosExplorer。這個組件旨在幫助用戶在不熟悉 SQL 的情況下,也能輕松管理 TDengine 集群。通過 taosExplorer,用戶可以輕松查看 TDengi…

Centos7安裝gitlab

環境準備: 操作系統:Centos7 內存:2G以上 磁盤:50G 安全:關閉防火墻,selinux 1、安裝GitLab所需依賴 yum -y install policycoreutils openssh-server openssh-clients postfix 2、設置postfix開機自啟…

【前端面經】云智慧一面

寫在前面:面經只是記錄博主遇到的題目。每題的答案在編寫文檔的時候已經有問過deepseek,它只是一種比較普世的答案,要學得深入還是靠自己 Q:手撕代碼,兩個有序數組排序 A: function mysort(arr1, arr2) {…

Leetcode 3568. Minimum Moves to Clean the Classroom

Leetcode 3568. Minimum Moves to Clean the Classroom 1. 解題思路2. 代碼實現 題目鏈接:3568. Minimum Moves to Clean the Classroom 1. 解題思路 這一題我的核心思路就是廣度優先遍歷遍歷剪枝。 顯然,我們可以給出一個廣度優先遍歷來給出所有可能…

Spring Boot,注解,@RestController

RestController 是 Spring MVC 中用于創建 RESTful Web 服務的核心注解。 RestController 核心知識點 REST 作用: RestController 是一個方便的組合注解,它結合了 Controller 和 ResponseBody 兩個注解。 Controller: 將類標記為一個控制器,使其能夠處理…

【計算機網絡】Linux下簡單的UDP服務器(超詳細)

套接字接口 我們把服務器封裝成一個類,當我們定義出一個服務器對象后需要馬上初始化服務器,而初始化服務器需要做的第一件事就是創建套接字。 🌎socket函數 這是Linux中創建套接字的系統調用,函數原型如下: int socket(int domain, int typ…

Fashion-MNIST LeNet訓練

前面使用線性神經網絡softmax 和 多層感知機進行圖像分類,本次我們使用LeNet 卷積神經網絡進行 訓練,期望能捕捉到圖像中的圖像結構信息,提高識別精度: import torch import torchvision from torchvision import transforms f…

EasyRTC嵌入式音視頻通信SDK助力1v1實時音視頻通話全場景應用

一、方案概述? 在數字化通信需求日益增長的今天,EasyRTC作為一款全平臺互通的實時視頻通話方案,實現了設備與平臺間的跨端連接。它支持微信小程序、APP、PC客戶端等多端協同,開發者通過該方案可快速搭建1v1實時音視頻通信系統,適…

查看make命令執行后涉及的預編譯宏定義的值

要查看 make 命令執行后涉及的預編譯宏定義(如 -D 定義的宏)及其值,可以采用以下方法: 1. 查看 Makefile 中的宏定義 直接檢查 Makefile 或相關構建腳本(如 configure、CMakeLists.txt),尋找 -…

【C/C++】面試常考題目

面試中最常考的數據結構與算法題,適合作為刷題的第一階段重點。 ? 分類 & 推薦題目列表(精選 70 道核心題) 一、數組 & 字符串(共 15 題) 題目類型LeetCode編號兩數之和哈希表#1盛最多水的容器雙指針#11三數…

【芯片學習】555

一、引腳作用 二、原理圖 三、等效原理圖 1.比較器 同相輸入端大于反相輸入端,輸出高電平,反之亦然 2.三極管 給它輸入高電平就可以導通 3.模擬電路部分 4.數字電路部分 這部分的核心是RS觸發器,R-reset代表0,set是置位代表1&am…

Linux《文件系統》

在之前的系統IO當中已經了解了“內存”級別的文件操作,了解了文件描述符、重定向、緩沖區等概念,在了解了這些的知識之后還封裝出了我們自己的libc庫。接下來在本篇當中將會將視角從內存轉向磁盤,研究文件在內存當中是如何進行存儲的&#xf…

Java-代碼段-http接口調用自身服務中的其他http接口(mock)-并建立socket連接發送和接收報文實例

最新版本更新 https://code.jiangjiesheng.cn/article/367?fromcsdn 推薦 《高并發 & 微服務 & 性能調優實戰案例100講 源碼下載》 1. controller入口 ApiOperation("模擬平臺端現場機socket交互過程,需要Authorization")PostMapping(path "/testS…

基于遞歸思想的系統架構圖自動化生成實踐

文章目錄 一、核心思想解析二、關鍵技術實現1. 動態布局算法2. 樣式規范集成3. MCP服務封裝三、典型應用場景四、最佳實踐建議五、擴展方向一、核心思想解析 本系統通過遞歸算法實現了Markdown層級結構到PPTX架構圖的自動轉換,其核心設計思想包含兩個維度: 數據結構遞歸:將…

Python包管理器 uv替代conda?

有人問:python的包管理器uv可以替代conda嗎? 搞數據和算法的把conda當寶貝,其他的場景能替代。 Python的包管理器有很多,pip是原配,uv是后起之秀,conda則主打數據科學。 uv替代pip似乎只是時間問題了,它…

使用pnpm、vite搭建Phaserjs的開發環境

首先,確保你已經安裝了 Node.js 和 npm。然后按照以下步驟操作: 一、使用pnpm初始化一個新的 Vite 項目 pnpm create vite 輸入名字 選擇模板,這里我選擇Vanilla,也可以選擇其他的比如vue 選擇語言 項目新建完成 二、安裝相關依賴 進入項…

JS逆向案例—喜馬拉雅xm-sign詳情頁爬取

JS逆向案例——喜馬拉雅xm-sign詳情頁爬取 聲明網站流程分析總結 聲明 本文章中所有內容僅供學習交流,抓包內容、敏感網址、數據接口均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關,若有侵權&am…

姜老師的MBTI課程:MBTI是可以轉變的

我們先來看內向和外向這條軸,I和E內向和外向受先天遺傳因素的影響還是比較大的,因為它事關到了你的硬件,也就是大腦的模型。但是我們在大五人格的排雷避坑和這套課程里面都強調了一個觀點,內向和外向各有優勢,也各有不…

進程同步:生產者-消費者 題目

正確答案: 問題類型: 經典生產者 - 消費者問題 同時涉及同步和互斥。 同步:生產者與消費者通過信號量協調生產 / 消費節奏(如緩沖區滿時生產者等待,空時消費者等待)。互斥:對共享緩沖區的訪問需…