上一篇:【Go實現】實踐GoF的23種設計模式:迭代器模式
簡單的分布式應用系統(示例代碼工程):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.
訪問者模式的目的是,解耦數據結構和算法,使得系統能夠在不改變現有代碼結構的基礎上,為對象新增一種新的操作。
上一篇介紹的迭代器模式也做到了數據結構和算法的解耦,不過它專注于遍歷算法。訪問者模式,則在遍歷的同時,將操作作用到數據結構上,一個常見的應用場景是語法樹的解析。
UML 結構
場景上下文
在簡單的分布式應用系統(示例代碼工程)中,db 模塊用來存儲服務注冊和監控信息,它是一個 key-value 數據庫。另外,我們給 db 模塊抽象出Table對象:
//demo/db/table.go packagedb //Table數據表定義 typeTablestruct{ namestring metadatamap[string]int//key為屬性名,value屬性值的索引,對應到record上存儲 recordsmap[interface{}]record iteratorFactoryTableIteratorFactory//默認使用隨機迭代器 }
目的是提供類似于關系型數據庫的按列查詢能力,比如:
上述的按列查詢只是等值比較,未來還可能會實現正則表達式匹配等方式,因此我們需要設計出可供未來擴展的接口。這種場景,使用訪問者模式正合適。
代碼實現
//demo/db/table_visitor.go packagedb //關鍵點1:定義表查詢的訪問者抽象接口,允許后續擴展查詢方式 typeTableVisitorinterface{ //關鍵點2:Visit方法以Element作為入參,這里的Element為Table對象 Visit(table*Table)([]interface{},error) } //關鍵點3:定義Visitor抽象接口的實現對象,這里FieldEqVisitor實現按列等值查詢邏輯 typeFieldEqVisitorstruct{ fieldstring valueinterface{} } //關鍵點4:為FieldEqVisitor定義Visit方法,實現具體的等值查詢邏輯 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{...} //關鍵點5:為Element定義Accept方法,入參為Visitor接口 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)) } }
總結實現訪問者模式的幾個關鍵點:
定義訪問者抽象接口,上述例子為TableVisitor, 目的是允許后續擴展表查詢方式。
訪問者抽象接口中,Visit方法以 Element 作為入參,上述例子中, Element 為Table對象。
為 Visitor 抽象接口定義具體的實現對象,上述例子為FieldEqVisitor。
在訪問者的Visit方法中實現具體的業務邏輯,上述例子中FieldEqVisitor.Visit(...)實現了按列等值查詢邏輯。
在被訪問者 Element 中定義 Accept 方法,以訪問者 Visitor 作為入參。上述例子中為Table.Accept(...)方法。
擴展
Go 風格實現
上述實現是典型的面向對象風格,下面以 Go 風格重新實現訪問者模式:
//demo/db/table_visitor_func.go packagedb //關鍵點1:定義一個訪問者函數類型 typeTableVisitorFuncfunc(table*Table)([]interface{},error) //關鍵點2:定義工廠方法,工廠方法返回的是一個訪問者函數,實現了具體的訪問邏輯 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 } } //關鍵點3:為Element定義Accept方法,入參為Visitor函數類型 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 風格的實現,利用了函數閉包的特點,更加簡潔了。
總結幾個實現關鍵點:
定義一個訪問者函數類型,函數簽名以 Element 作為入參,上述例子為TableVisitorFunc類型。
定義一個工廠方法,工廠方法返回的是具體的訪問訪問者函數,上述例子為NewFieldEqVisitorFunc方法。這里利用了函數閉包的特性,在訪問者函數中直接引用工廠方法的入參,與FieldEqVisitor中持有兩個成員屬性的效果一樣。
為 Element 定義 Accept 方法,入參為 Visitor 函數類型 ,上述例子是Table.AcceptFunc(...)方法。
與迭代器模式結合
訪問者模式經常與迭代器模式一起使用。比如上述例子中,如果你定義的 Visitor 實現不在 db 包內,那么就無法直接訪問Table的數據,這時就需要通過Table提供的迭代器來實現。
在簡單的分布式應用系統(示例代碼工程)中,db 模塊存儲的服務注冊信息如下:
//demo/service/registry/model/service_profile.go packagemodel //ServiceProfileRecord存儲在數據庫里的類型 typeServiceProfileRecordstruct{ Idstring//服務ID TypeServiceType//服務類型 StatusServiceStatus//服務狀態 Ipstring//服務IP Portint//服務端口 RegionIdstring//服務所屬regionId Priorityint//服務優先級,范圍0~100,值越低,優先級越高 Loadint//服務負載,負載越高表示服務處理的業務壓力越大 }
現在,我們要查詢符合指定ServiceId和ServiceType的服務記錄,可以這么實現一個 Visitor:
//demo/service/registry/model/service_profile.go packagemodel typeServiceProfileVisitorstruct{ svcIdstring svcTypeServiceType } func(s*ServiceProfileVisitor)Visit(table*db.Table)([]interface{},error){ varresult[]interface{} //通過迭代器來遍歷Table的所有數據 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 }
典型應用場景
k8s 中,kubectl 通過訪問者模式來處理用戶定義的各類資源。
編譯器中,通常使用訪問者模式來實現對語法樹解析,比如 LLVM。
希望對一個復雜的數據結構執行某些操作,并支持后續擴展。
優缺點
優點
數據結構和操作算法解耦,符合單一職責原則。
支持對數據結構擴展多種操作,具備較強的可擴展性,符合開閉原則。
缺點
訪問者模式某種程度上,要求數據結構必須對外暴露其內在實現,否則訪問者就無法遍歷其中數據(可以結合迭代器模式來解決該問題)。
如果被訪問對象內的數據結構變更,可能要更新所有的訪問者實現。
與其他模式的關聯
訪問者模式 經常和迭代器模式一起使用,使得被訪問對象無須向外暴露內在數據結構。
也經常和組合模式一起使用,比如在語法樹解析中,遞歸訪問和解析樹的每個節點(節點組合成樹)。
文章配圖
可以在用Keynote畫出手繪風格的配圖中找到文章的繪圖方法。
審核編輯:湯梓紅
-
代碼
+關注
關注
30文章
4828瀏覽量
69063 -
設計模式
+關注
關注
0文章
53瀏覽量
8655 -
迭代器
+關注
關注
0文章
44瀏覽量
4347
原文標題:【Go實現】實踐GoF的23種設計模式:訪問者模式
文章出處:【微信號:yuanrunzi,微信公眾號:元閏子的邀請】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
Command模式與動態語言
![Command<b class='flag-5'>模式</b>與動態語言](https://file1.elecfans.com//web2/M00/A5/E9/wKgZomUMOsKAOstdAAASJm0zrVI220.gif)
在 Java8 環境下實現觀察者模式的實例分析
![在 Java8 環境下實現觀察<b class='flag-5'>者</b><b class='flag-5'>模式</b>的實例分析](https://file.elecfans.com/web2/M00/49/FF/pYYBAGKhvIWAEm7nAAArzbYpOtY654.png)
Java設計模式(二十一):中介者模式
嵌入式軟件設計模式 好文值得收藏
GoF設計模式之觀察者模式
GoF設計模式之代理模式
UVM設計模式之訪問者模式
![UVM設計<b class='flag-5'>模式</b><b class='flag-5'>之</b><b class='flag-5'>訪問者</b><b class='flag-5'>模式</b>](https://file1.elecfans.com/web2/M00/90/1B/wKgZomTVjwWAXK5iAAP4jI4N2fo731.jpg)
評論