Go設(shè)計模式之代理模式圖文詳解
代理模式
亦稱: Proxy
代理模式是一種結(jié)構(gòu)型設(shè)計模式, 讓你能夠提供對象的替代品或其占位符。 代理控制著對于原對象的訪問, 并允許在將請求提交給對象前后進(jìn)行一些處理。
問題
為什么要控制對于某個對象的訪問呢? 舉個例子: 有這樣一個消耗大量系統(tǒng)資源的巨型對象, 你只是偶爾需要使用它, 并非總是需要。
數(shù)據(jù)庫查詢有可能會非常緩慢。
你可以實(shí)現(xiàn)延遲初始化: 在實(shí)際有需要時再創(chuàng)建該對象。 對象的所有客戶端都要執(zhí)行延遲初始代碼。 不幸的是, 這很可能會帶來很多重復(fù)代碼。
在理想情況下, 我們希望將代碼直接放入對象的類中, 但這并非總是能實(shí)現(xiàn): 比如類可能是第三方封閉庫的一部分。
解決方案
代理模式建議新建一個與原服務(wù)對象接口相同的代理類, 然后更新應(yīng)用以將代理對象傳遞給所有原始對象客戶端。 代理類接收到客戶端請求后會創(chuàng)建實(shí)際的服務(wù)對象, 并將所有工作委派給它。
代理將自己偽裝成數(shù)據(jù)庫對象, 可在客戶端或?qū)嶋H數(shù)據(jù)庫對象不知情的情況下處理延遲初始化和緩存查詢結(jié)果的工作。
這有什么好處呢? 如果需要在類的主要業(yè)務(wù)邏輯前后執(zhí)行一些工作, 你無需修改類就能完成這項(xiàng)工作。 由于代理實(shí)現(xiàn)的接口與原類相同, 因此你可將其傳遞給任何一個使用實(shí)際服務(wù)對象的客戶端。
真實(shí)世界類比
信用卡和現(xiàn)金在支付過程中的用處相同。
信用卡是銀行賬戶的代理, 銀行賬戶則是一大捆現(xiàn)金的代理。 它們都實(shí)現(xiàn)了同樣的接口, 均可用于進(jìn)行支付。 消費(fèi)者會非常滿意, 因?yàn)椴槐仉S身攜帶大量現(xiàn)金; 商店老板同樣會十分高興, 因?yàn)榻灰资杖肽芤噪娮踊姆绞竭M(jìn)入商店的銀行賬戶中, 無需擔(dān)心存款時出現(xiàn)現(xiàn)金丟失或被搶劫的情況。
代理模式結(jié)構(gòu)
服務(wù)接口 (Service Interface) 聲明了服務(wù)接口。 代理必須遵循該接口才能偽裝成服務(wù)對象。
服務(wù) (Service) 類提供了一些實(shí)用的業(yè)務(wù)邏輯。
代理 (Proxy) 類包含一個指向服務(wù)對象的引用成員變量。 代理完成其任務(wù) (例如延遲初始化、 記錄日志、 訪問控制和緩存等) 后會將請求傳遞給服務(wù)對象。
通常情況下, 代理會對其服務(wù)對象的整個生命周期進(jìn)行管理。
客戶端 (Client) 能通過同一接口與服務(wù)或代理進(jìn)行交互, 所以你可在一切需要服務(wù)對象的代碼中使用代理。
偽代碼
本例演示如何使用代理模式在第三方騰訊視頻 (TencentVideo, 代碼示例中記為 TV) 程序庫中添加延遲初始化和緩存。
使用代理緩沖服務(wù)結(jié)果。
程序庫提供了視頻下載類。 但是該類的效率非常低。 如果客戶端程序多次請求同一視頻, 程序庫會反復(fù)下載該視頻, 而不會將首次下載的文件緩存下來復(fù)用。
代理類實(shí)現(xiàn)和原下載器相同的接口, 并將所有工作委派給原下載器。 不過, 代理類會保存所有的文件下載記錄, 如果程序多次請求同一文件, 它會返回緩存的文件。
// 遠(yuǎn)程服務(wù)接口。 interface ThirdPartyTVLib is method listVideos() method getVideoInfo(id) method downloadVideo(id) // 服務(wù)連接器的具體實(shí)現(xiàn)。該類的方法可以向騰訊視頻請求信息。請求速度取決于 // 用戶和騰訊視頻的互聯(lián)網(wǎng)連接情況。如果同時發(fā)送大量請求,即使所請求的信息 // 一模一樣,程序的速度依然會減慢。 class ThirdPartyTVClass implements ThirdPartyTVLib is method listVideos() is // 向騰訊視頻發(fā)送一個 API 請求。 method getVideoInfo(id) is // 獲取某個視頻的元數(shù)據(jù)。 method downloadVideo(id) is // 從騰訊視頻下載一個視頻文件。 // 為了節(jié)省網(wǎng)絡(luò)帶寬,我們可以將請求結(jié)果緩存下來并保存一段時間。但你可能無 // 法直接將這些代碼放入服務(wù)類中。比如該類可能是第三方程序庫的一部分或其簽 // 名是`final(最終)`。因此我們會在一個實(shí)現(xiàn)了服務(wù)類接口的新代理類中放入 // 緩存代碼。當(dāng)代理類接收到真實(shí)請求后,才會將其委派給服務(wù)對象。 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ù)對象交互的 GUI 類不需要改變,前提是它僅通過接口與服務(wù)對 // 象交互。我們可以安全地傳遞一個代理對象來代替真實(shí)服務(wù)對象,因?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)行時對代理進(jìn)行配置。 class Application is method init() is aTVService = new ThirdPartyTVClass() aTVProxy = new CachedTVClass(aTVService) manager = new TVManager(aTVProxy) manager.reactOnUserInput()
代理模式適合應(yīng)用場景
使用代理模式的方式多種多樣, 我們來看看最常見的幾種。
延遲初始化 (虛擬代理)。 如果你有一個偶爾使用的重量級服務(wù)對象, 一直保持該對象運(yùn)行會消耗系統(tǒng)資源時, 可使用代理模式。
你無需在程序啟動時就創(chuàng)建該對象, 可將對象的初始化延遲到真正有需要的時候。
訪問控制 (保護(hù)代理)。 如果你只希望特定客戶端使用服務(wù)對象, 這里的對象可以是操作系統(tǒng)中非常重要的部分, 而客戶端則是各種已啟動的程序 (包括惡意程序), 此時可使用代理模式。
代理可僅在客戶端憑據(jù)滿足要求時將請求傳遞給服務(wù)對象。
本地執(zhí)行遠(yuǎn)程服務(wù) (遠(yuǎn)程代理)。 適用于服務(wù)對象位于遠(yuǎn)程服務(wù)器上的情形。在這種情形中, 代理通過網(wǎng)絡(luò)傳遞客戶端請求, 負(fù)責(zé)處理所有與網(wǎng)絡(luò)相關(guān)的復(fù)雜細(xì)節(jié)。
記錄日志請求 (日志記錄代理)。 適用于當(dāng)你需要保存對于服務(wù)對象的請求歷史記錄時。代理可以在向服務(wù)傳遞請求前進(jìn)行記錄。
緩存請求結(jié)果 (緩存代理)。 適用于需要緩存客戶請求結(jié)果并對緩存生命周期進(jìn)行管理時, 特別是當(dāng)返回結(jié)果的體積非常大時。代理可對重復(fù)請求所需的相同結(jié)果進(jìn)行緩存, 還可使用請求參數(shù)作為索引緩存的鍵值。
智能引用。 可在沒有客戶端使用某個重量級對象時立即銷毀該對象。代理會將所有獲取了指向服務(wù)對象或其結(jié)果的客戶端記錄在案。 代理會時不時地遍歷各個客戶端, 檢查它們是否仍在運(yùn)行。 如果相應(yīng)的客戶端列表為空, 代理就會銷毀該服務(wù)對象, 釋放底層系統(tǒng)資源。
代理還可以記錄客戶端是否修改了服務(wù)對象。 其他客戶端還可以復(fù)用未修改的對象。
實(shí)現(xiàn)方式
- 如果沒有現(xiàn)成的服務(wù)接口, 你就需要創(chuàng)建一個接口來實(shí)現(xiàn)代理和服務(wù)對象的可交換性。 從服務(wù)類中抽取接口并非總是可行的, 因?yàn)槟阈枰獙Ψ?wù)的所有客戶端進(jìn)行修改, 讓它們使用接口。 備選計劃是將代理作為服務(wù)類的子類, 這樣代理就能繼承服務(wù)的所有接口了。
- 創(chuàng)建代理類, 其中必須包含一個存儲指向服務(wù)的引用的成員變量。 通常情況下, 代理負(fù)責(zé)創(chuàng)建服務(wù)并對其整個生命周期進(jìn)行管理。 在一些特殊情況下, 客戶端會通過構(gòu)造函數(shù)將服務(wù)傳遞給代理。
- 根據(jù)需求實(shí)現(xiàn)代理方法。 在大部分情況下, 代理在完成一些任務(wù)后應(yīng)將工作委派給服務(wù)對象。
- 可以考慮新建一個構(gòu)建方法來判斷客戶端可獲取的是代理還是實(shí)際服務(wù)。 你可以在代理類中創(chuàng)建一個簡單的靜態(tài)方法, 也可以創(chuàng)建一個完整的工廠方法。
- 可以考慮為服務(wù)對象實(shí)現(xiàn)延遲初始化。
代理模式優(yōu)缺點(diǎn)
你可以在客戶端毫無察覺的情況下控制服務(wù)對象。
如果客戶端對服務(wù)對象的生命周期沒有特殊要求, 你可以對生命周期進(jìn)行管理。
即使服務(wù)對象還未準(zhǔn)備好或不存在, 代理也可以正常工作。
開閉原則。 你可以在不對服務(wù)或客戶端做出修改的情況下創(chuàng)建新代理。
代碼可能會變得復(fù)雜, 因?yàn)樾枰陆ㄔS多類。
服務(wù)響應(yīng)可能會延遲。
代碼示例
Go設(shè)計模式之代理模式講解和代碼示例_Golang_腳本之家 (jb51.net)
到此這篇關(guān)于Go設(shè)計模式之代理模式圖文詳解的文章就介紹到這了,更多相關(guān)Go代理模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang結(jié)合mysql設(shè)置最大連接數(shù)和最大空閑連接數(shù)
本文介紹golang?中連接MySQL時,如何設(shè)置最大連接數(shù)和最大空閑連接數(shù),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù)的開發(fā)筆記分享
這篇文章主要為大家詳細(xì)介紹了如何使用Golang實(shí)現(xiàn)短網(wǎng)址/短鏈服務(wù),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解一下2023-05-05基于Golang實(shí)現(xiàn)內(nèi)存數(shù)據(jù)庫的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何基于Golang實(shí)現(xiàn)內(nèi)存數(shù)據(jù)庫,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,需要的小伙伴可以參考一下2023-03-03深入解析Go語言編程中slice切片結(jié)構(gòu)
這篇文章主要介紹了Go語言編程中slice切片結(jié)構(gòu),其中Append方法的用法介紹較為詳細(xì),需要的朋友可以參考下2015-10-10golang解析json數(shù)據(jù)的4種方法總結(jié)
在日常工作中每一名開發(fā)者,不管是前端還是后端,都經(jīng)常使用 JSON,下面這篇文章主要給大家介紹了關(guān)于golang解析json數(shù)據(jù)的4種方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06