Go設(shè)計(jì)模式之代理模式圖文詳解
代理模式
亦稱: Proxy
代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式, 讓你能夠提供對(duì)象的替代品或其占位符。 代理控制著對(duì)于原對(duì)象的訪問, 并允許在將請求提交給對(duì)象前后進(jìn)行一些處理。

問題
為什么要控制對(duì)于某個(gè)對(duì)象的訪問呢? 舉個(gè)例子: 有這樣一個(gè)消耗大量系統(tǒng)資源的巨型對(duì)象, 你只是偶爾需要使用它, 并非總是需要。

數(shù)據(jù)庫查詢有可能會(huì)非常緩慢。
你可以實(shí)現(xiàn)延遲初始化: 在實(shí)際有需要時(shí)再創(chuàng)建該對(duì)象。 對(duì)象的所有客戶端都要執(zhí)行延遲初始代碼。 不幸的是, 這很可能會(huì)帶來很多重復(fù)代碼。
在理想情況下, 我們希望將代碼直接放入對(duì)象的類中, 但這并非總是能實(shí)現(xiàn): 比如類可能是第三方封閉庫的一部分。
解決方案
代理模式建議新建一個(gè)與原服務(wù)對(duì)象接口相同的代理類, 然后更新應(yīng)用以將代理對(duì)象傳遞給所有原始對(duì)象客戶端。 代理類接收到客戶端請求后會(huì)創(chuàng)建實(shí)際的服務(wù)對(duì)象, 并將所有工作委派給它。

代理將自己偽裝成數(shù)據(jù)庫對(duì)象, 可在客戶端或?qū)嶋H數(shù)據(jù)庫對(duì)象不知情的情況下處理延遲初始化和緩存查詢結(jié)果的工作。
這有什么好處呢? 如果需要在類的主要業(yè)務(wù)邏輯前后執(zhí)行一些工作, 你無需修改類就能完成這項(xiàng)工作。 由于代理實(shí)現(xiàn)的接口與原類相同, 因此你可將其傳遞給任何一個(gè)使用實(shí)際服務(wù)對(duì)象的客戶端。
真實(shí)世界類比

信用卡和現(xiàn)金在支付過程中的用處相同。
信用卡是銀行賬戶的代理, 銀行賬戶則是一大捆現(xiàn)金的代理。 它們都實(shí)現(xiàn)了同樣的接口, 均可用于進(jìn)行支付。 消費(fèi)者會(huì)非常滿意, 因?yàn)椴槐仉S身攜帶大量現(xiàn)金; 商店老板同樣會(huì)十分高興, 因?yàn)榻灰资杖肽芤噪娮踊姆绞竭M(jìn)入商店的銀行賬戶中, 無需擔(dān)心存款時(shí)出現(xiàn)現(xiàn)金丟失或被搶劫的情況。
代理模式結(jié)構(gòu)

服務(wù)接口 (Service Interface) 聲明了服務(wù)接口。 代理必須遵循該接口才能偽裝成服務(wù)對(duì)象。
服務(wù) (Service) 類提供了一些實(shí)用的業(yè)務(wù)邏輯。
代理 (Proxy) 類包含一個(gè)指向服務(wù)對(duì)象的引用成員變量。 代理完成其任務(wù) (例如延遲初始化、 記錄日志、 訪問控制和緩存等) 后會(huì)將請求傳遞給服務(wù)對(duì)象。
通常情況下, 代理會(huì)對(duì)其服務(wù)對(duì)象的整個(gè)生命周期進(jìn)行管理。
客戶端 (Client) 能通過同一接口與服務(wù)或代理進(jìn)行交互, 所以你可在一切需要服務(wù)對(duì)象的代碼中使用代理。
偽代碼
本例演示如何使用代理模式在第三方騰訊視頻 (TencentVideo, 代碼示例中記為 TV) 程序庫中添加延遲初始化和緩存。

使用代理緩沖服務(wù)結(jié)果。
程序庫提供了視頻下載類。 但是該類的效率非常低。 如果客戶端程序多次請求同一視頻, 程序庫會(huì)反復(fù)下載該視頻, 而不會(huì)將首次下載的文件緩存下來復(fù)用。
代理類實(shí)現(xiàn)和原下載器相同的接口, 并將所有工作委派給原下載器。 不過, 代理類會(huì)保存所有的文件下載記錄, 如果程序多次請求同一文件, 它會(huì)返回緩存的文件。
// 遠(yuǎn)程服務(wù)接口。
interface ThirdPartyTVLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
// 服務(wù)連接器的具體實(shí)現(xiàn)。該類的方法可以向騰訊視頻請求信息。請求速度取決于
// 用戶和騰訊視頻的互聯(lián)網(wǎng)連接情況。如果同時(shí)發(fā)送大量請求,即使所請求的信息
// 一模一樣,程序的速度依然會(huì)減慢。
class ThirdPartyTVClass implements ThirdPartyTVLib is
method listVideos() is
// 向騰訊視頻發(fā)送一個(gè) API 請求。
method getVideoInfo(id) is
// 獲取某個(gè)視頻的元數(shù)據(jù)。
method downloadVideo(id) is
// 從騰訊視頻下載一個(gè)視頻文件。
// 為了節(jié)省網(wǎng)絡(luò)帶寬,我們可以將請求結(jié)果緩存下來并保存一段時(shí)間。但你可能無
// 法直接將這些代碼放入服務(wù)類中。比如該類可能是第三方程序庫的一部分或其簽
// 名是`final(最終)`。因此我們會(huì)在一個(gè)實(shí)現(xiàn)了服務(wù)類接口的新代理類中放入
// 緩存代碼。當(dāng)代理類接收到真實(shí)請求后,才會(huì)將其委派給服務(wù)對(duì)象。
class CachedTVClass implements ThirdPartyTVLib is
private field service: ThirdPartyTVLib
private field listCache, videoCache
field needReset
constructor CachedTVClass(service: ThirdPartyTVLib) is
this.service = service
method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache
method getVideoInfo(id) is
if (videoCache == null || needReset)
videoCache = service.getVideoInfo(id)
return videoCache
method downloadVideo(id) is
if (!downloadExists(id) || needReset)
service.downloadVideo(id)
// 之前直接與服務(wù)對(duì)象交互的 GUI 類不需要改變,前提是它僅通過接口與服務(wù)對(duì)
// 象交互。我們可以安全地傳遞一個(gè)代理對(duì)象來代替真實(shí)服務(wù)對(duì)象,因?yàn)樗鼈兌紝?shí)
// 現(xiàn)了相同的接口。
class TVManager is
protected field service: ThirdPartyTVLib
constructor TVManager(service: ThirdPartyTVLib) is
this.service = service
method renderVideoPage(id) is
info = service.getVideoInfo(id)
// 渲染視頻頁面。
method renderListPanel() is
list = service.listVideos()
// 渲染視頻縮略圖列表。
method reactOnUserInput() is
renderVideoPage()
renderListPanel()
// 程序可在運(yùn)行時(shí)對(duì)代理進(jìn)行配置。
class Application is
method init() is
aTVService = new ThirdPartyTVClass()
aTVProxy = new CachedTVClass(aTVService)
manager = new TVManager(aTVProxy)
manager.reactOnUserInput()代理模式適合應(yīng)用場景
使用代理模式的方式多種多樣, 我們來看看最常見的幾種。
延遲初始化 (虛擬代理)。 如果你有一個(gè)偶爾使用的重量級(jí)服務(wù)對(duì)象, 一直保持該對(duì)象運(yùn)行會(huì)消耗系統(tǒng)資源時(shí), 可使用代理模式。
你無需在程序啟動(dòng)時(shí)就創(chuàng)建該對(duì)象, 可將對(duì)象的初始化延遲到真正有需要的時(shí)候。
訪問控制 (保護(hù)代理)。 如果你只希望特定客戶端使用服務(wù)對(duì)象, 這里的對(duì)象可以是操作系統(tǒng)中非常重要的部分, 而客戶端則是各種已啟動(dòng)的程序 (包括惡意程序), 此時(shí)可使用代理模式。
代理可僅在客戶端憑據(jù)滿足要求時(shí)將請求傳遞給服務(wù)對(duì)象。
本地執(zhí)行遠(yuǎn)程服務(wù) (遠(yuǎn)程代理)。 適用于服務(wù)對(duì)象位于遠(yuǎn)程服務(wù)器上的情形。在這種情形中, 代理通過網(wǎng)絡(luò)傳遞客戶端請求, 負(fù)責(zé)處理所有與網(wǎng)絡(luò)相關(guān)的復(fù)雜細(xì)節(jié)。
記錄日志請求 (日志記錄代理)。 適用于當(dāng)你需要保存對(duì)于服務(wù)對(duì)象的請求歷史記錄時(shí)。代理可以在向服務(wù)傳遞請求前進(jìn)行記錄。
緩存請求結(jié)果 (緩存代理)。 適用于需要緩存客戶請求結(jié)果并對(duì)緩存生命周期進(jìn)行管理時(shí), 特別是當(dāng)返回結(jié)果的體積非常大時(shí)。代理可對(duì)重復(fù)請求所需的相同結(jié)果進(jìn)行緩存, 還可使用請求參數(shù)作為索引緩存的鍵值。
智能引用。 可在沒有客戶端使用某個(gè)重量級(jí)對(duì)象時(shí)立即銷毀該對(duì)象。代理會(huì)將所有獲取了指向服務(wù)對(duì)象或其結(jié)果的客戶端記錄在案。 代理會(huì)時(shí)不時(shí)地遍歷各個(gè)客戶端, 檢查它們是否仍在運(yùn)行。 如果相應(yīng)的客戶端列表為空, 代理就會(huì)銷毀該服務(wù)對(duì)象, 釋放底層系統(tǒng)資源。
代理還可以記錄客戶端是否修改了服務(wù)對(duì)象。 其他客戶端還可以復(fù)用未修改的對(duì)象。
實(shí)現(xiàn)方式
- 如果沒有現(xiàn)成的服務(wù)接口, 你就需要?jiǎng)?chuàng)建一個(gè)接口來實(shí)現(xiàn)代理和服務(wù)對(duì)象的可交換性。 從服務(wù)類中抽取接口并非總是可行的, 因?yàn)槟阈枰獙?duì)服務(wù)的所有客戶端進(jìn)行修改, 讓它們使用接口。 備選計(jì)劃是將代理作為服務(wù)類的子類, 這樣代理就能繼承服務(wù)的所有接口了。
- 創(chuàng)建代理類, 其中必須包含一個(gè)存儲(chǔ)指向服務(wù)的引用的成員變量。 通常情況下, 代理負(fù)責(zé)創(chuàng)建服務(wù)并對(duì)其整個(gè)生命周期進(jìn)行管理。 在一些特殊情況下, 客戶端會(huì)通過構(gòu)造函數(shù)將服務(wù)傳遞給代理。
- 根據(jù)需求實(shí)現(xiàn)代理方法。 在大部分情況下, 代理在完成一些任務(wù)后應(yīng)將工作委派給服務(wù)對(duì)象。
- 可以考慮新建一個(gè)構(gòu)建方法來判斷客戶端可獲取的是代理還是實(shí)際服務(wù)。 你可以在代理類中創(chuàng)建一個(gè)簡單的靜態(tài)方法, 也可以創(chuàng)建一個(gè)完整的工廠方法。
- 可以考慮為服務(wù)對(duì)象實(shí)現(xiàn)延遲初始化。
代理模式優(yōu)缺點(diǎn)
你可以在客戶端毫無察覺的情況下控制服務(wù)對(duì)象。
如果客戶端對(duì)服務(wù)對(duì)象的生命周期沒有特殊要求, 你可以對(duì)生命周期進(jìn)行管理。
即使服務(wù)對(duì)象還未準(zhǔn)備好或不存在, 代理也可以正常工作。
開閉原則。 你可以在不對(duì)服務(wù)或客戶端做出修改的情況下創(chuàng)建新代理。
代碼可能會(huì)變得復(fù)雜, 因?yàn)樾枰陆ㄔS多類。
服務(wù)響應(yīng)可能會(huì)延遲。
代碼示例
Go設(shè)計(jì)模式之代理模式講解和代碼示例_Golang_腳本之家 (jb51.net)
到此這篇關(guān)于Go設(shè)計(jì)模式之代理模式圖文詳解的文章就介紹到這了,更多相關(guān)Go代理模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang結(jié)合mysql設(shè)置最大連接數(shù)和最大空閑連接數(shù)
本文介紹golang?中連接MySQL時(shí),如何設(shè)置最大連接數(shù)和最大空閑連接數(shù),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù)的開發(fā)筆記分享
這篇文章主要為大家詳細(xì)介紹了如何使用Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-05-05
基于Golang實(shí)現(xiàn)內(nèi)存數(shù)據(jù)庫的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何基于Golang實(shí)現(xiàn)內(nèi)存數(shù)據(jù)庫,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以參考一下2023-03-03
深入解析Go語言編程中slice切片結(jié)構(gòu)
這篇文章主要介紹了Go語言編程中slice切片結(jié)構(gòu),其中Append方法的用法介紹較為詳細(xì),需要的朋友可以參考下2015-10-10
golang解析json數(shù)據(jù)的4種方法總結(jié)
在日常工作中每一名開發(fā)者,不管是前端還是后端,都經(jīng)常使用 JSON,下面這篇文章主要給大家介紹了關(guān)于golang解析json數(shù)據(jù)的4種方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06

