上一篇:【Go實現】實踐GoF的23種設計模式:訪問者模式
簡單的分布式應用系統(示例代碼工程):https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation
簡介
GoF 對代理模式(Proxy Pattern)的定義如下:
Provide a surrogate or placeholder for another object to control access to it.
也即,代理模式為一個對象提供一種代理以控制對該對象的訪問。
它是一個使用率非常高的設計模式,在現實生活中,也是很常見。比如,演唱會門票黃牛。假設你需要看一場演唱會,但官網上門票已經售罄,于是就當天到現場通過黃牛高價買了一張。在這個例子中,黃牛就相當于演唱會門票的代理,在正式渠道無法購買門票的情況下,你通過代理完成了該目標。
從演唱會門票的例子我們也能看出,使用代理模式的關鍵在于,當 Client 不方便直接訪問一個對象時,提供一個代理對象控制該對象的訪問。Client 實際上訪問的是代理對象,代理對象會將 Client 的請求轉給本體對象去處理。
UML 結構
場景上下文
在簡單的分布式應用系統(示例代碼工程)中,db 模塊用來存儲服務注冊和監控信息,它是一個 key-value 數據庫。為了提升訪問數據庫的性能,我們決定為它新增一層緩存:
另外,我們希望客戶端在使用數據庫時,并不感知緩存的存在,這些,代理模式可以做到。
代碼實現
//demo/db/cache.go packagedb //關鍵點1:定義代理對象,實現被代理對象的接口 typeCacheProxystruct{ //關鍵點2:組合被代理對象,這里應該是抽象接口,提升可擴展性 dbDb cachesync.Map//key為tableName,value為sync.Map[key:primaryId,value:interface{}] hitint missint } //關鍵點3:在具體接口實現上,嵌入代理本身的邏輯 func(c*CacheProxy)Query(tableNamestring,primaryKeyinterface{},resultinterface{})error{ cache,ok:=c.cache.Load(tableName) ifok{ ifrecord,ok:=cache.(*sync.Map).Load(primaryKey);ok{ c.hit++ result=record returnnil } } c.miss++ iferr:=c.db.Query(tableName,primaryKey,result);err!=nil{ returnerr } cache.(*sync.Map).Store(primaryKey,result) returnnil } func(c*CacheProxy)Insert(tableNamestring,primaryKeyinterface{},recordinterface{})error{ iferr:=c.db.Insert(tableName,primaryKey,record);err!=nil{ returnerr } cache,ok:=c.cache.Load(tableName) if!ok{ returnnil } cache.(*sync.Map).Store(primaryKey,record) returnnil } ... //關鍵點4:代理也可以有自己特有方法,提供一些輔助的功能 func(c*CacheProxy)Hit()int{ returnc.hit } func(c*CacheProxy)Miss()int{ returnc.miss } ...
客戶端這樣使用:
//客戶端只看到抽象的Db接口 funcclient(dbDb){ table:=NewTable("region"). WithType(reflect.TypeOf(new(testRegion))). WithTableIteratorFactory(NewRandomTableIteratorFactory()) db.CreateTable(table) table.Insert(1,&testRegion{Id:1,Name:"region"}) result:=new(testRegion) db.Query("region",1,result) } funcmain(){ //關鍵點5:在初始化階段,完成緩存的實例化,并依賴注入到客戶端 cache:=NewCacheProxy(&memoryDb{tables:sync.Map{}}) client(cache) }
本例子中,Subject 是Db接口,Proxy 是CacheProxy對象,SubjectImpl 是memoryDb對象:
總結實現代理模式的幾個關鍵點:
定義代理對象,實現被代理對象的接口。本例子中,前者是CacheProxy對象,后者是Db接口。
代理對象組合被代理對象,這里組合的應該是抽象接口,讓代理的可擴展性更高些。本例子中,CacheProxy對象組合了Db接口。
代理對象在具體接口實現上,嵌入代理本身的邏輯。本例子中,CacheProxy在Query、Insert等方法中,加入了緩存sync.Map的讀寫邏輯。
代理對象也可以有自己特有方法,提供一些輔助的功能。本例子中,CacheProxy新增了Hit、Miss等方法用于統計緩存的命中率。
最后,在初始化階段,完成代理的實例化,并依賴注入到客戶端。這要求,客戶端依賴抽象接口,而不是具體實現,否則代理就不透明了。
擴展
Go 標準庫中的反向代理
代理模式最典型的應用場景是遠程代理,其中,反向代理又是最常用的一種。
以 Web 應用為例,反向代理位于 Web 服務器前面,將客戶端(例如 Web 瀏覽器)請求轉發后端的 Web 服務器。反向代理通常用于幫助提高安全性、性能和可靠性,比如負載均衡、SSL 安全鏈接。
Go 標準庫的 net 包也提供了反向代理,ReverseProxy,位于net/http/httputil/reverseproxy.go下,實現http.Handler接口。http.Handler提供了處理 Http 請求的能力,也即相當于 Http 服務器。那么,對應到 UML 結構圖中,http.Handler就是 Subject,ReverseProxy就是 Proxy:
下面列出ReverseProxy的一些核心代碼:
//net/http/httputil/reverseproxy.go packagehttputil typeReverseProxystruct{ //修改前端請求,然后通過Transport將修改后的請求轉發給后端 Directorfunc(*http.Request) //可理解為Subject,通過Transport來調用被代理對象的ServeHTTP方法處理請求 Transporthttp.RoundTripper //修改后端響應,并將修改后的響應返回給前端 ModifyResponsefunc(*http.Response)error //錯誤處理 ErrorHandlerfunc(http.ResponseWriter,*http.Request,error) ... } func(p*ReverseProxy)ServeHTTP(rwhttp.ResponseWriter,req*http.Request){ //初始化transport transport:=p.Transport iftransport==nil{ transport=http.DefaultTransport } ... //修改前端請求 p.Director(outreq) ... //將請求轉發給后端 res,err:=transport.RoundTrip(outreq) ... //修改后端響應 if!p.modifyResponse(rw,res,outreq){ return } ... //給前端返回響應 err=p.copyResponse(rw,res.Body,p.flushInterval(res)) ... }
ReverseProxy就是典型的代理模式實現,其中,遠程代理無法直接引用后端的對象引用,因此這里通過引入Transport來遠程訪問后端服務,可以將Transport理解為 Subject。
可以這么使用ReverseProxy:
funcproxy(c*gin.Context){ remote,err:=url.Parse("https://yrunz.com") iferr!=nil{ panic(err) } proxy:=httputil.NewSingleHostReverseProxy(remote) proxy.Director=func(req*http.Request){ req.Header=c.Request.Header req.Host=remote.Host req.URL.Scheme=remote.Scheme req.URL.Host=remote.Host req.URL.Path=c.Param("proxyPath") } proxy.ServeHTTP(c.Writer,c.Request) } funcmain(){ r:=gin.Default() r.Any("/*proxyPath",proxy) r.Run(":8080") }
典型應用場景
遠程代理(remote proxy),遠程代理適用于提供服務的對象處在遠程的機器上,通過普通的函數調用無法使用服務,需要經過遠程代理來完成。因為并不能直接訪問本體對象,所有遠程代理對象通常不會直接持有本體對象的引用,而是持有遠端機器的地址,通過網絡協議去訪問本體對象。
虛擬代理(virtual proxy),在程序設計中常常會有一些重量級的服務對象,如果一直持有該對象實例會非常消耗系統資源,這時可以通過虛擬代理來對該對象進行延遲初始化。
保護代理(protection proxy),保護代理用于控制對本體對象的訪問,常用于需要給 Client 的訪問加上權限驗證的場景。
緩存代理(cache proxy),緩存代理主要在 Client 與本體對象之間加上一層緩存,用于加速本體對象的訪問,常見于連接數據庫的場景。
智能引用(smart reference),智能引用為本體對象的訪問提供了額外的動作,常見的實現為 C++ 中的智能指針,為對象的訪問提供了計數功能,當訪問對象的計數為 0 時銷毀該對象。
優缺點
優點
可以在客戶端不感知的情況下,控制訪問對象,比如遠程訪問、增加緩存、安全等。
符合開閉原則,可以在不修改客戶端和被代理對象的前提下,增加新的代理;也可以在不修改客戶端和代理的前提下,更換被代理對象。
缺點
作為遠程代理時,因為多了一次轉發,會影響請求的時延。
與其他模式的關聯
從結構上看,裝飾模式和 代理模式 具有很高的相似性,但是兩種所強調的點不一樣。前者強調的是為本體對象添加新的功能,后者強調的是對本體對象的訪問控制。
文章配圖
可以在用Keynote畫出手繪風格的配圖中找到文章的繪圖方法。
-
代碼
+關注
關注
30文章
4828瀏覽量
69055 -
設計模式
+關注
關注
0文章
53瀏覽量
8655 -
Client
+關注
關注
0文章
10瀏覽量
8828 -
代理模式
+關注
關注
0文章
4瀏覽量
1789
原文標題:【Go實現】實踐GoF的23種設計模式:代理模式
文章出處:【微信號:yuanrunzi,微信公眾號:元閏子的邀請】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
適配器模式和代理模式的區別
23種基本的設計模式總結
Command模式與動態語言
![Command<b class='flag-5'>模式</b>與動態語言](https://file1.elecfans.com//web2/M00/A5/E9/wKgZomUMOsKAOstdAAASJm0zrVI220.gif)
適配器模式和代理模式的區別
![適配器<b class='flag-5'>模式</b>和<b class='flag-5'>代理</b><b class='flag-5'>模式</b>的區別](https://file1.elecfans.com//web2/M00/A7/27/wKgZomUMQrWAd34jAAAvQ2bAN0k459.png)
GoF設計模式之觀察者模式
實踐GoF的23種設計模式:命令模式簡介
設計模式中代理模式的使用場景
![設計<b class='flag-5'>模式</b>中<b class='flag-5'>代理</b><b class='flag-5'>模式</b>的使用場景](https://file1.elecfans.com/web2/M00/A7/31/wKgaomUiTSmAWIcbAAA9s3ZJpgI835.jpg)
評論