上一篇:【Go實(shí)現(xiàn)】實(shí)踐GoF的23種設(shè)計(jì)模式:訪問者模式
簡(jiǎn)單的分布式應(yīng)用系統(tǒng)(示例代碼工程):https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation
簡(jiǎn)介
GoF 對(duì)代理模式(Proxy Pattern)的定義如下:
Provide a surrogate or placeholder for another object to control access to it.
也即,代理模式為一個(gè)對(duì)象提供一種代理以控制對(duì)該對(duì)象的訪問。
它是一個(gè)使用率非常高的設(shè)計(jì)模式,在現(xiàn)實(shí)生活中,也是很常見。比如,演唱會(huì)門票黃牛。假設(shè)你需要看一場(chǎng)演唱會(huì),但官網(wǎng)上門票已經(jīng)售罄,于是就當(dāng)天到現(xiàn)場(chǎng)通過黃牛高價(jià)買了一張。在這個(gè)例子中,黃牛就相當(dāng)于演唱會(huì)門票的代理,在正式渠道無法購(gòu)買門票的情況下,你通過代理完成了該目標(biāo)。
從演唱會(huì)門票的例子我們也能看出,使用代理模式的關(guān)鍵在于,當(dāng) Client 不方便直接訪問一個(gè)對(duì)象時(shí),提供一個(gè)代理對(duì)象控制該對(duì)象的訪問。Client 實(shí)際上訪問的是代理對(duì)象,代理對(duì)象會(huì)將 Client 的請(qǐng)求轉(zhuǎn)給本體對(duì)象去處理。
UML 結(jié)構(gòu)
場(chǎng)景上下文
在簡(jiǎn)單的分布式應(yīng)用系統(tǒng)(示例代碼工程)中,db 模塊用來存儲(chǔ)服務(wù)注冊(cè)和監(jiān)控信息,它是一個(gè) key-value 數(shù)據(jù)庫。為了提升訪問數(shù)據(jù)庫的性能,我們決定為它新增一層緩存:
另外,我們希望客戶端在使用數(shù)據(jù)庫時(shí),并不感知緩存的存在,這些,代理模式可以做到。
代碼實(shí)現(xiàn)
//demo/db/cache.go packagedb //關(guān)鍵點(diǎn)1:定義代理對(duì)象,實(shí)現(xiàn)被代理對(duì)象的接口 typeCacheProxystruct{ //關(guān)鍵點(diǎn)2:組合被代理對(duì)象,這里應(yīng)該是抽象接口,提升可擴(kuò)展性 dbDb cachesync.Map//key為tableName,value為sync.Map[key:primaryId,value:interface{}] hitint missint } //關(guān)鍵點(diǎn)3:在具體接口實(shí)現(xiàn)上,嵌入代理本身的邏輯 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 } ... //關(guān)鍵點(diǎn)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(){ //關(guān)鍵點(diǎn)5:在初始化階段,完成緩存的實(shí)例化,并依賴注入到客戶端 cache:=NewCacheProxy(&memoryDb{tables:sync.Map{}}) client(cache) }
本例子中,Subject 是Db接口,Proxy 是CacheProxy對(duì)象,SubjectImpl 是memoryDb對(duì)象:
總結(jié)實(shí)現(xiàn)代理模式的幾個(gè)關(guān)鍵點(diǎn):
定義代理對(duì)象,實(shí)現(xiàn)被代理對(duì)象的接口。本例子中,前者是CacheProxy對(duì)象,后者是Db接口。
代理對(duì)象組合被代理對(duì)象,這里組合的應(yīng)該是抽象接口,讓代理的可擴(kuò)展性更高些。本例子中,CacheProxy對(duì)象組合了Db接口。
代理對(duì)象在具體接口實(shí)現(xiàn)上,嵌入代理本身的邏輯。本例子中,CacheProxy在Query、Insert等方法中,加入了緩存sync.Map的讀寫邏輯。
代理對(duì)象也可以有自己特有方法,提供一些輔助的功能。本例子中,CacheProxy新增了Hit、Miss等方法用于統(tǒng)計(jì)緩存的命中率。
最后,在初始化階段,完成代理的實(shí)例化,并依賴注入到客戶端。這要求,客戶端依賴抽象接口,而不是具體實(shí)現(xiàn),否則代理就不透明了。
擴(kuò)展
Go 標(biāo)準(zhǔn)庫中的反向代理
代理模式最典型的應(yīng)用場(chǎng)景是遠(yuǎn)程代理,其中,反向代理又是最常用的一種。
以 Web 應(yīng)用為例,反向代理位于 Web 服務(wù)器前面,將客戶端(例如 Web 瀏覽器)請(qǐng)求轉(zhuǎn)發(fā)后端的 Web 服務(wù)器。反向代理通常用于幫助提高安全性、性能和可靠性,比如負(fù)載均衡、SSL 安全鏈接。
Go 標(biāo)準(zhǔn)庫的 net 包也提供了反向代理,ReverseProxy,位于net/http/httputil/reverseproxy.go下,實(shí)現(xiàn)http.Handler接口。http.Handler提供了處理 Http 請(qǐng)求的能力,也即相當(dāng)于 Http 服務(wù)器。那么,對(duì)應(yīng)到 UML 結(jié)構(gòu)圖中,http.Handler就是 Subject,ReverseProxy就是 Proxy:
下面列出ReverseProxy的一些核心代碼:
//net/http/httputil/reverseproxy.go packagehttputil typeReverseProxystruct{ //修改前端請(qǐng)求,然后通過Transport將修改后的請(qǐng)求轉(zhuǎn)發(fā)給后端 Directorfunc(*http.Request) //可理解為Subject,通過Transport來調(diào)用被代理對(duì)象的ServeHTTP方法處理請(qǐng)求 Transporthttp.RoundTripper //修改后端響應(yīng),并將修改后的響應(yīng)返回給前端 ModifyResponsefunc(*http.Response)error //錯(cuò)誤處理 ErrorHandlerfunc(http.ResponseWriter,*http.Request,error) ... } func(p*ReverseProxy)ServeHTTP(rwhttp.ResponseWriter,req*http.Request){ //初始化transport transport:=p.Transport iftransport==nil{ transport=http.DefaultTransport } ... //修改前端請(qǐng)求 p.Director(outreq) ... //將請(qǐng)求轉(zhuǎn)發(fā)給后端 res,err:=transport.RoundTrip(outreq) ... //修改后端響應(yīng) if!p.modifyResponse(rw,res,outreq){ return } ... //給前端返回響應(yīng) err=p.copyResponse(rw,res.Body,p.flushInterval(res)) ... }
ReverseProxy就是典型的代理模式實(shí)現(xiàn),其中,遠(yuǎn)程代理無法直接引用后端的對(duì)象引用,因此這里通過引入Transport來遠(yuǎn)程訪問后端服務(wù),可以將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") }
典型應(yīng)用場(chǎng)景
遠(yuǎn)程代理(remote proxy),遠(yuǎn)程代理適用于提供服務(wù)的對(duì)象處在遠(yuǎn)程的機(jī)器上,通過普通的函數(shù)調(diào)用無法使用服務(wù),需要經(jīng)過遠(yuǎn)程代理來完成。因?yàn)椴⒉荒苤苯釉L問本體對(duì)象,所有遠(yuǎn)程代理對(duì)象通常不會(huì)直接持有本體對(duì)象的引用,而是持有遠(yuǎn)端機(jī)器的地址,通過網(wǎng)絡(luò)協(xié)議去訪問本體對(duì)象。
虛擬代理(virtual proxy),在程序設(shè)計(jì)中常常會(huì)有一些重量級(jí)的服務(wù)對(duì)象,如果一直持有該對(duì)象實(shí)例會(huì)非常消耗系統(tǒng)資源,這時(shí)可以通過虛擬代理來對(duì)該對(duì)象進(jìn)行延遲初始化。
保護(hù)代理(protection proxy),保護(hù)代理用于控制對(duì)本體對(duì)象的訪問,常用于需要給 Client 的訪問加上權(quán)限驗(yàn)證的場(chǎng)景。
緩存代理(cache proxy),緩存代理主要在 Client 與本體對(duì)象之間加上一層緩存,用于加速本體對(duì)象的訪問,常見于連接數(shù)據(jù)庫的場(chǎng)景。
智能引用(smart reference),智能引用為本體對(duì)象的訪問提供了額外的動(dòng)作,常見的實(shí)現(xiàn)為 C++ 中的智能指針,為對(duì)象的訪問提供了計(jì)數(shù)功能,當(dāng)訪問對(duì)象的計(jì)數(shù)為 0 時(shí)銷毀該對(duì)象。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
可以在客戶端不感知的情況下,控制訪問對(duì)象,比如遠(yuǎn)程訪問、增加緩存、安全等。
符合開閉原則,可以在不修改客戶端和被代理對(duì)象的前提下,增加新的代理;也可以在不修改客戶端和代理的前提下,更換被代理對(duì)象。
缺點(diǎn)
作為遠(yuǎn)程代理時(shí),因?yàn)槎嗔艘淮无D(zhuǎn)發(fā),會(huì)影響請(qǐng)求的時(shí)延。
與其他模式的關(guān)聯(lián)
從結(jié)構(gòu)上看,裝飾模式和 代理模式 具有很高的相似性,但是兩種所強(qiáng)調(diào)的點(diǎn)不一樣。前者強(qiáng)調(diào)的是為本體對(duì)象添加新的功能,后者強(qiáng)調(diào)的是對(duì)本體對(duì)象的訪問控制。
文章配圖
可以在用Keynote畫出手繪風(fēng)格的配圖中找到文章的繪圖方法。
-
代碼
+關(guān)注
關(guān)注
30文章
4722瀏覽量
68231 -
設(shè)計(jì)模式
+關(guān)注
關(guān)注
0文章
53瀏覽量
8620 -
Client
+關(guān)注
關(guān)注
0文章
10瀏覽量
8749 -
代理模式
+關(guān)注
關(guān)注
0文章
3瀏覽量
1754
原文標(biāo)題:【Go實(shí)現(xiàn)】實(shí)踐GoF的23種設(shè)計(jì)模式:代理模式
文章出處:【微信號(hào):yuanrunzi,微信公眾號(hào):元閏子的邀請(qǐng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論