在閱讀 Kubernetes: kubectl
源碼時看到有關(guān)訪問者設(shè)計模式的運用。訪問者模式是行為型設(shè)計模式的一種,本篇文章將對訪問者模式做一個介紹。
首先,給出一個比較粗糙的示例。
實現(xiàn)程序,功能如下:
(相關(guān)資料圖)
當(dāng)男人成功時,顯示我有一個好老婆;當(dāng)女人成功時,顯示我有一個有愛的丈夫;當(dāng)男人開心時,顯示我有一個玩具;當(dāng)女人開心時,顯示我有一個有愛的丈夫;當(dāng)男人傷心時,顯示我丟了玩具;當(dāng)女人傷心時,顯示我丟了有愛的丈夫;
基于上述描述,實現(xiàn)示例代碼:
type man struct {action string}type woman struct {action string}func (m *man) status() {if m.action == "happy" {fmt.Println("I have a toy")}if m.action == "sad" {fmt.Println("I lost my toy")}if m.action == "success" {fmt.Println("I have a great wife")}}func (w *woman) status() {if w.action == "happy" {fmt.Println("I have a lovely husband")}if w.action == "sad" {fmt.Println("I lost my lovely husband")}if w.action == "success" {fmt.Println("I have a lovely husband")}}func main() {m := man{action: "sad",}m.status()}
示例代碼實現(xiàn)了想要的功能??梢钥闯觯腥撕团祟惗季哂?action
且行為不一樣。
那么,如果增加 action
,比如當(dāng)男人戀愛時,顯示我終于脫單了
;當(dāng)女人戀愛時,顯示終于有人愛我了
;就需要更新男人和女人類的 status
方法,這不符合開閉原則。
既然不符合開閉原則,那怎么樣才能解耦對象和行為,使得行為的變化不會影響到對象實現(xiàn)呢?
進一步看上述實現(xiàn),男人和女人類是行為的主體,行為有成功時的行為,開心時的行為,傷心時的行為...
既然是解耦,那先把行為拿出來作為接口,在以具體的行為類實現(xiàn)接口??纯丛趺窗研袨轭惡蛯ο箢惙珠_。
實現(xiàn)代碼:
type status interface {manStatus()womanStatus()}type happy struct{}type sad struct{}type success struct{}func (h *happy) manStatus() {fmt.Println("I have a toy")}func (s *sad) manStatus() {fmt.Println("I lost my toy")}func (s *success) manStatus() {fmt.Println("I have a great wife")}func (h *happy) womanStatus() {fmt.Println("I have a lovely husband")}func (s *sad) womanStatus() {fmt.Println("I lost my lovely husband")}func (s *success) womanStatus() {fmt.Println("I have a lovely husband")}type man struct {}type woman struct {}
這里把對象類的 action
屬性拿掉,相應(yīng)的把 action
轉(zhuǎn)換為行為類。行為類實現(xiàn)了接口,其中定義了 manStatus()
和 womanStatus()
方法。
這樣我們拆成了兩個類,對象類和行為類。怎么把這兩個類聯(lián)系起來呢?看代碼,男人類和男人類的行為,女人類和女人類的行為是有聯(lián)系的,初始化對象類和行為類:
m := man{}s := sad{}// m.demo(s)
應(yīng)該通過 demo
將對象和行為類聯(lián)系起來,然后調(diào)用 s
的 manStatus()
方法完成關(guān)聯(lián):
func (m *man) accept(s status) {s.manStatus()}func (w *woman) accept(s status) {s.womanStatus()}func main() {m := &man{}s := &sad{}m.accept(s)}
將 demo
重命名為 accept
作為對象類的方法,在其中調(diào)用行為類的方法。
至此,我們已經(jīng)實現(xiàn)了訪問者模式。有人可能會問了,我怎么還是沒看出來?我們在分析上述代碼。
有兩個類,對象類和行為類。對象類實現(xiàn) accept
方法,其是動作的主體,將對象類和行為類關(guān)聯(lián)起來。行為類根據(jù)不同對象實現(xiàn)行為方法,其是行為的主體,行為是建立在對象之上的。
基于上述分析,我們繼續(xù)改造代碼:
type visitor interface {manStatus()womanStatus()}type happyVisitor struct{}type sadVisitor struct{}type successVisitor struct{}func (h *happyVisitor) manStatus() {fmt.Println("I have a toy")}func (s *sadVisitor) manStatus() {fmt.Println("I lost my toy")}func (s *successVisitor) manStatus() {fmt.Println("I have a great wife")}func (h *happyVisitor) womanStatus() {fmt.Println("I have a lovely husband")}func (s *sadVisitor) womanStatus() {fmt.Println("I lost my lovely husband")}func (s *successVisitor) womanStatus() {fmt.Println("I have a lovely husband")}type man struct {}type woman struct {}func (m *man) accept(s visitor) {s.manStatus()}func (w *woman) accept(s visitor) {s.womanStatus()}
結(jié)構(gòu)基本沒變,只是重命名了一下,更容易分清主體。
上述示例對于行為類相比于對象類是主的體驗還不明顯,重新改寫代碼:
type visitor interface {manStatus(*man)womanStatus(*woman)}type happyVisitor struct{}type sadVisitor struct{}type successVisitor struct{}func (h *happyVisitor) manStatus(m *man) {fmt.Println(m.name, "I have a toy")}func (s *sadVisitor) manStatus(m *man) {fmt.Println(m.name, "I lost my toy")}func (s *successVisitor) manStatus(m *man) {fmt.Println(m.name, "I have a great wife")}type man struct {name string}func (m *man) accept(s visitor) {s.manStatus(m)}func main() {m := &man{"hxia"}s := &sadVisitor{}m.accept(s)}
改寫后的代碼,行為類會訪問對象類的 name
屬性,對于行為類來說,對象類就是數(shù)據(jù),是屬性。
基于此,畫出訪問者設(shè)計模式的 UML 圖:
訪問者模式在 GoF 合著的《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》中的定義是:
允許一個或多個操作應(yīng)用到一組對象上,解耦操作和對象本身Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure
2. VisitorFunc 和訪問者模式前面介紹了訪問者模式,從定義看訪問者模式通過將一個或多個操作應(yīng)用到一組對象上,以實現(xiàn)對象和操作的解耦。這里需要重點關(guān)注的點是一組對象。
一組意味著對象具有相似性,且結(jié)構(gòu)是穩(wěn)定的。試想如果男人和女人類中,女人沒有傷心時的行為,那就沒辦法將其歸為一組對象,或者需要實現(xiàn) fake 傷心以保持對象的相似性。
既然定義的是一組,當(dāng)然對象也可以是一個。假定對象是一個,使用函數(shù) Visitor
改寫上述訪問者模式代碼:
type VisitorFunc func(man)func happyVisitor(m man) {fmt.Println(m.name, "I have a good thing")}func sadVisitor(m man) {fmt.Println(m.name, "I have a bad thing")}func successVisitor(m man) {fmt.Println(m.name, "I finish a thing")}type man struct {name string}func (m man) accept(v VisitorFunc) {v(m)}func main() {m := man{"hxia"}m.accept(sadVisitor)}
這么改寫在于簡單,去掉類取而代之的是函數(shù),用函數(shù)實現(xiàn)了具體的 Visitor
。當(dāng)然,這樣的 Visitor
只能處理一種操作。
類似地,如果一組對象的行為是一樣的,也可以用函數(shù) Visitor
來實現(xiàn):
type VisitorFunc func(person)func happyVisitor(p person) {fmt.Println(p.getName(), "I have a good thing")}func sadVisitor(p person) {fmt.Println(p.getName(), "I have a bad thing")}func successVisitor(p person) {fmt.Println(p.getName(), "I finish a thing")}type person interface {getName() string}type man struct {name string}type woman struct {name string}func (m man) getName() string {return m.name}func (w woman) getName() string {return w.name}func (m *man) accept(v VisitorFunc) {v(m)}func (w *woman) accept(v VisitorFunc) {v(w)}func main() {m := &man{"hxia"}m.accept(sadVisitor)}
2.1 嵌套 Visitor如果操作作用于一個對象,可以用函數(shù) Visitor
來簡化實現(xiàn)。如果多個操作嵌套的作用于對象上,那么可以使用嵌套 Visitor
實現(xiàn),其效果類似于多個小應(yīng)用訪問數(shù)據(jù)庫以實現(xiàn)某個功能。
代碼示例如下:
type VisitorFunc func(*man) errorfunc happyVisitor(m *man) error {fmt.Println(m.name)return nil}func sadVisitor(m *man) error {fmt.Println(m.age)return nil}func successVisitor(m *man) error {fmt.Println(m.sex)return nil}func validationFunc(m *man) error {if m.name == "" {return errors.New("empty name")}return nil}type man struct {name stringage intsex string}type Visitor interface {Visit(VisitorFunc) error}func (m *man) Visit(fn VisitorFunc) error {fmt.Println("in man")if err := fn(m); err != nil {return err}fmt.Println("out man")return nil}type validationVisitor struct {visitor Visitor}func (v validationVisitor) Visit(fn VisitorFunc) error {return v.visitor.Visit(func(m *man) error {fmt.Println("in validation")if m.name == "" {return errors.New("empty name")}if err := fn(m); err != nil {return err}fmt.Println("out validation")return nil})}type errorVisitor struct {visitor Visitor}func (v errorVisitor) Visit(fn VisitorFunc) error {return v.visitor.Visit(func(m *man) error {fmt.Println("in error")if err := fn(m); err != nil {return err}fmt.Println("out error")return nil})}type ageVisitor struct {visitor Visitor}func (v ageVisitor) Visit(fn VisitorFunc) error {return v.visitor.Visit(func(m *man) error {fmt.Println("in age")if err := fn(m); err != nil {return err}fmt.Println(m.name, m.age)fmt.Println("out age")return nil})}type VisitorList []Visitorfunc (l VisitorList) Visit(fn VisitorFunc) error {for i := range l {if err := l[i].Visit(fn); err != nil {return err}}return nil}func main() {var visitor Visitorm1 := &man{name: "hxia", age: 18}m2 := &man{name: "huyun", age: 29}m3 := &man{name: "troy", age: 25}visitors := []Visitor{m1, m2, m3}visitor = VisitorList(visitors)visitor = validationVisitor{visitor: visitor}visitor = errorVisitor{visitor: visitor}visitor = ageVisitor{visitor: visitor}visitor.Visit(happyVisitor)}
代碼有點長,其基本是 Kubernetes:kubectl
訪問者模式的主體,把它看懂了,再去看 kubectl
的訪問者模式實現(xiàn)就不難了。
首先,對象實現(xiàn)了 Visitor
(類似于上例的 accept
),接受函數(shù) Visitor
,函數(shù) Visitor
訪問對象操作:
func (m *man) Visit(fn VisitorFunc) error {...}
接著,將多個對象裝入 VisitorList
,且該 VisitorList
也實現(xiàn)了 Visitor
接口方法。這么做是為了遍歷訪問每個對象:
func (l VisitorList) Visit(fn VisitorFunc) error {for i := range l {if err := l[i].Visit(fn); err != nil {return err}}return nil}
然后,是在函數(shù) Visitor
操作對象之后對對象做一些其它操作,這里定義了 validationVisitor
用來驗證對象的名字是否為空:
func (v validationVisitor) Visit(fn VisitorFunc) error {...}// mainvisitor = validationVisitor{visitor: visitor}visitor.Visit(happyVisitor)
通過層層嵌套 Visitor
實現(xiàn)對象的嵌套操作。
這些代碼了解了,再去看 Kubernetes:kubectl
應(yīng)該不難了,代碼在 這里。
優(yōu)點
訪問者模式適用于對象復(fù)雜且具有較多操作的場景,使用訪問者模式可解耦對象和操作,簡化對象職責(zé)。訪問者模式側(cè)重在訪問者,對于訪問者而言,多加幾個訪問者操作不影響對象的實現(xiàn),符合開閉原則。缺點
由于訪問者模式解耦了對象和它的操作,對象的屬性暴露給訪問者,打破了對象和操作的封裝。訪問者模式對對象不友好,如果對象結(jié)構(gòu)不穩(wěn)定,很難使用訪問者模式。同時,如果要加入新對象,需要訪問者接口,實現(xiàn)和對象都要改,不符合開閉原則。4. 參考文章訪問者模式GO 編程模式:K8S VISITOR 模式