上一篇:【Go實現(xiàn)】實踐GoF的23種設(shè)計模式:迭代器模式
簡單的分布式應(yīng)用系統(tǒng)(示例代碼工程):https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation
簡介
GoF 對訪問者模式(Visitor Pattern)的定義如下:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
訪問者模式的目的是,解耦數(shù)據(jù)結(jié)構(gòu)和算法,使得系統(tǒng)能夠在不改變現(xiàn)有代碼結(jié)構(gòu)的基礎(chǔ)上,為對象新增一種新的操作。
上一篇介紹的迭代器模式也做到了數(shù)據(jù)結(jié)構(gòu)和算法的解耦,不過它專注于遍歷算法。訪問者模式,則在遍歷的同時,將操作作用到數(shù)據(jù)結(jié)構(gòu)上,一個常見的應(yīng)用場景是語法樹的解析。
UML 結(jié)構(gòu)

場景上下文
在簡單的分布式應(yīng)用系統(tǒng)(示例代碼工程)中,db 模塊用來存儲服務(wù)注冊和監(jiān)控信息,它是一個 key-value 數(shù)據(jù)庫。另外,我們給 db 模塊抽象出Table對象:
//demo/db/table.go
packagedb
//Table數(shù)據(jù)表定義
typeTablestruct{
namestring
metadatamap[string]int//key為屬性名,value屬性值的索引,對應(yīng)到record上存儲
recordsmap[interface{}]record
iteratorFactoryTableIteratorFactory//默認(rèn)使用隨機迭代器
}
目的是提供類似于關(guān)系型數(shù)據(jù)庫的按列查詢能力,比如:

上述的按列查詢只是等值比較,未來還可能會實現(xiàn)正則表達式匹配等方式,因此我們需要設(shè)計出可供未來擴展的接口。這種場景,使用訪問者模式正合適。
代碼實現(xiàn)
//demo/db/table_visitor.go
packagedb
//關(guān)鍵點1:定義表查詢的訪問者抽象接口,允許后續(xù)擴展查詢方式
typeTableVisitorinterface{
//關(guān)鍵點2:Visit方法以Element作為入?yún)?,這里的Element為Table對象
Visit(table*Table)([]interface{},error)
}
//關(guān)鍵點3:定義Visitor抽象接口的實現(xiàn)對象,這里FieldEqVisitor實現(xiàn)按列等值查詢邏輯
typeFieldEqVisitorstruct{
fieldstring
valueinterface{}
}
//關(guān)鍵點4:為FieldEqVisitor定義Visit方法,實現(xiàn)具體的等值查詢邏輯
func(f*FieldEqVisitor)Visit(table*Table)([]interface{},error){
result:=make([]interface{},0)
idx,ok:=table.metadata[f.field]
if!ok{
returnnil,ErrRecordNotFound
}
for_,r:=rangetable.records{
ifreflect.DeepEqual(r.values[idx],f.value){
result=append(result,r)
}
}
iflen(result)==0{
returnnil,ErrRecordNotFound
}
returnresult,nil
}
funcNewFieldEqVisitor(fieldstring,valueinterface{})*FieldEqVisitor{
return&FieldEqVisitor{
field:field,
value:value,
}
}
//demo/db/table.go
packagedb
typeTablestruct{...}
//關(guān)鍵點5:為Element定義Accept方法,入?yún)閂isitor接口
func(t*Table)Accept(visitorTableVisitor)([]interface{},error){
returnvisitor.Visit(t)
}
客戶端可以這么使用:
funcclient(){
table:=NewTable("testRegion").WithType(reflect.TypeOf(new(testRegion)))
table.Insert(1,&testRegion{Id:1,Name:"beijing"})
table.Insert(2,&testRegion{Id:2,Name:"beijing"})
table.Insert(3,&testRegion{Id:3,Name:"guangdong"})
visitor:=NewFieldEqVisitor("name","beijing")
result,err:=table.Accept(visitor)
iferr!=nil{
t.Error(err)
}
iflen(result)!=2{
t.Errorf("visitfailed,want2,got%d",len(result))
}
}
總結(jié)實現(xiàn)訪問者模式的幾個關(guān)鍵點:
定義訪問者抽象接口,上述例子為TableVisitor, 目的是允許后續(xù)擴展表查詢方式。
訪問者抽象接口中,Visit方法以 Element 作為入?yún)ⅲ鲜隼又校?Element 為Table對象。
為 Visitor 抽象接口定義具體的實現(xiàn)對象,上述例子為FieldEqVisitor。
在訪問者的Visit方法中實現(xiàn)具體的業(yè)務(wù)邏輯,上述例子中FieldEqVisitor.Visit(...)實現(xiàn)了按列等值查詢邏輯。
在被訪問者 Element 中定義 Accept 方法,以訪問者 Visitor 作為入?yún)?。上述例子中為Table.Accept(...)方法。
擴展
Go 風(fēng)格實現(xiàn)
上述實現(xiàn)是典型的面向?qū)ο箫L(fēng)格,下面以 Go 風(fēng)格重新實現(xiàn)訪問者模式:
//demo/db/table_visitor_func.go
packagedb
//關(guān)鍵點1:定義一個訪問者函數(shù)類型
typeTableVisitorFuncfunc(table*Table)([]interface{},error)
//關(guān)鍵點2:定義工廠方法,工廠方法返回的是一個訪問者函數(shù),實現(xiàn)了具體的訪問邏輯
funcNewFieldEqVisitorFunc(fieldstring,valueinterface{})TableVisitorFunc{
returnfunc(table*Table)([]interface{},error){
result:=make([]interface{},0)
idx,ok:=table.metadata[field]
if!ok{
returnnil,ErrRecordNotFound
}
for_,r:=rangetable.records{
ifreflect.DeepEqual(r.values[idx],value){
result=append(result,r)
}
}
iflen(result)==0{
returnnil,ErrRecordNotFound
}
returnresult,nil
}
}
//關(guān)鍵點3:為Element定義Accept方法,入?yún)閂isitor函數(shù)類型
func(t*Table)AcceptFunc(visitorFuncTableVisitorFunc)([]interface{},error){
returnvisitorFunc(t)
}
客戶端可以這么使用:
funcclient(){
table:=NewTable("testRegion").WithType(reflect.TypeOf(new(testRegion)))
table.Insert(1,&testRegion{Id:1,Name:"beijing"})
table.Insert(2,&testRegion{Id:2,Name:"beijing"})
table.Insert(3,&testRegion{Id:3,Name:"guangdong"})
result,err:=table.AcceptFunc(NewFieldEqVisitorFunc("name","beijing"))
iferr!=nil{
t.Error(err)
}
iflen(result)!=2{
t.Errorf("visitfailed,want2,got%d",len(result))
}
}
Go 風(fēng)格的實現(xiàn),利用了函數(shù)閉包的特點,更加簡潔了。
總結(jié)幾個實現(xiàn)關(guān)鍵點:
定義一個訪問者函數(shù)類型,函數(shù)簽名以 Element 作為入?yún)?,上述例子為TableVisitorFunc類型。
定義一個工廠方法,工廠方法返回的是具體的訪問訪問者函數(shù),上述例子為NewFieldEqVisitorFunc方法。這里利用了函數(shù)閉包的特性,在訪問者函數(shù)中直接引用工廠方法的入?yún)ⅲcFieldEqVisitor中持有兩個成員屬性的效果一樣。
為 Element 定義 Accept 方法,入?yún)?Visitor 函數(shù)類型 ,上述例子是Table.AcceptFunc(...)方法。
與迭代器模式結(jié)合
訪問者模式經(jīng)常與迭代器模式一起使用。比如上述例子中,如果你定義的 Visitor 實現(xiàn)不在 db 包內(nèi),那么就無法直接訪問Table的數(shù)據(jù),這時就需要通過Table提供的迭代器來實現(xiàn)。
在簡單的分布式應(yīng)用系統(tǒng)(示例代碼工程)中,db 模塊存儲的服務(wù)注冊信息如下:
//demo/service/registry/model/service_profile.go
packagemodel
//ServiceProfileRecord存儲在數(shù)據(jù)庫里的類型
typeServiceProfileRecordstruct{
Idstring//服務(wù)ID
TypeServiceType//服務(wù)類型
StatusServiceStatus//服務(wù)狀態(tài)
Ipstring//服務(wù)IP
Portint//服務(wù)端口
RegionIdstring//服務(wù)所屬regionId
Priorityint//服務(wù)優(yōu)先級,范圍0~100,值越低,優(yōu)先級越高
Loadint//服務(wù)負載,負載越高表示服務(wù)處理的業(yè)務(wù)壓力越大
}
現(xiàn)在,我們要查詢符合指定ServiceId和ServiceType的服務(wù)記錄,可以這么實現(xiàn)一個 Visitor:
//demo/service/registry/model/service_profile.go
packagemodel
typeServiceProfileVisitorstruct{
svcIdstring
svcTypeServiceType
}
func(s*ServiceProfileVisitor)Visit(table*db.Table)([]interface{},error){
varresult[]interface{}
//通過迭代器來遍歷Table的所有數(shù)據(jù)
iter:=table.Iterator()
foriter.HasNext(){
profile:=new(ServiceProfileRecord)
iferr:=iter.Next(profile);err!=nil{
returnnil,err
}
//先匹配ServiceId,如果一致則無須匹配ServiceType
ifprofile.Id!=""&&profile.Id==s.svcId{
result=append(result,profile)
continue
}
//ServiceId匹配不上,再匹配ServiceType
ifprofile.Type!=""&&profile.Type==s.svcType{
result=append(result,profile)
}
}
returnresult,nil
}
典型應(yīng)用場景
k8s 中,kubectl 通過訪問者模式來處理用戶定義的各類資源。
編譯器中,通常使用訪問者模式來實現(xiàn)對語法樹解析,比如 LLVM。
希望對一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu)執(zhí)行某些操作,并支持后續(xù)擴展。
優(yōu)缺點
優(yōu)點
數(shù)據(jù)結(jié)構(gòu)和操作算法解耦,符合單一職責(zé)原則。
支持對數(shù)據(jù)結(jié)構(gòu)擴展多種操作,具備較強的可擴展性,符合開閉原則。
缺點
訪問者模式某種程度上,要求數(shù)據(jù)結(jié)構(gòu)必須對外暴露其內(nèi)在實現(xiàn),否則訪問者就無法遍歷其中數(shù)據(jù)(可以結(jié)合迭代器模式來解決該問題)。
如果被訪問對象內(nèi)的數(shù)據(jù)結(jié)構(gòu)變更,可能要更新所有的訪問者實現(xiàn)。
與其他模式的關(guān)聯(lián)
訪問者模式 經(jīng)常和迭代器模式一起使用,使得被訪問對象無須向外暴露內(nèi)在數(shù)據(jù)結(jié)構(gòu)。
也經(jīng)常和組合模式一起使用,比如在語法樹解析中,遞歸訪問和解析樹的每個節(jié)點(節(jié)點組合成樹)。
文章配圖
可以在用Keynote畫出手繪風(fēng)格的配圖中找到文章的繪圖方法。
審核編輯:湯梓紅
-
代碼
+關(guān)注
關(guān)注
30文章
4968瀏覽量
73965 -
設(shè)計模式
+關(guān)注
關(guān)注
0文章
53瀏覽量
8990 -
迭代器
+關(guān)注
關(guān)注
0文章
45瀏覽量
4623
原文標(biāo)題:【Go實現(xiàn)】實踐GoF的23種設(shè)計模式:訪問者模式
文章出處:【微信號:yuanrunzi,微信公眾號:元閏子的邀請】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Command模式與動態(tài)語言
在 Java8 環(huán)境下實現(xiàn)觀察者模式的實例分析
Java設(shè)計模式(二十一):中介者模式
配置Nginx訪問日志
嵌入式軟件設(shè)計模式 好文值得收藏
GoF給裝飾者模式的定義
GoF設(shè)計模式之觀察者模式
GoF設(shè)計模式之代理模式
設(shè)計模式:訪問者設(shè)計模式
設(shè)計模式創(chuàng)造性:建造者模式
UVM設(shè)計模式之訪問者模式
實踐GoF的23種設(shè)計模式:備忘錄模式
實踐GoF的23種設(shè)計模式:解釋器模式
GoF設(shè)計模式之訪問者模式
評論