在閱讀 Kubernetes: kubectl
源碼時(shí)看到有關(guān)訪問(wèn)者設(shè)計(jì)模式的運(yùn)用。訪問(wèn)者模式是行為型設(shè)計(jì)模式的一種,本篇文章將對(duì)訪問(wèn)者模式做一個(gè)介紹。
首先,給出一個(gè)比較粗糙的示例。
實(shí)現(xiàn)程序,功能如下:
(相關(guān)資料圖)
當(dāng)男人成功時(shí),顯示我有一個(gè)好老婆;當(dāng)女人成功時(shí),顯示我有一個(gè)有愛(ài)的丈夫;當(dāng)男人開(kāi)心時(shí),顯示我有一個(gè)玩具;當(dāng)女人開(kāi)心時(shí),顯示我有一個(gè)有愛(ài)的丈夫;當(dāng)男人傷心時(shí),顯示我丟了玩具;當(dāng)女人傷心時(shí),顯示我丟了有愛(ài)的丈夫;
基于上述描述,實(shí)現(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()}
示例代碼實(shí)現(xiàn)了想要的功能。可以看出,男人和女人類(lèi)都具有 action
且行為不一樣。
那么,如果增加 action
,比如當(dāng)男人戀愛(ài)時(shí),顯示我終于脫單了
;當(dāng)女人戀愛(ài)時(shí),顯示終于有人愛(ài)我了
;就需要更新男人和女人類(lèi)的 status
方法,這不符合開(kāi)閉原則。
既然不符合開(kāi)閉原則,那怎么樣才能解耦對(duì)象和行為,使得行為的變化不會(huì)影響到對(duì)象實(shí)現(xiàn)呢?
進(jìn)一步看上述實(shí)現(xiàn),男人和女人類(lèi)是行為的主體,行為有成功時(shí)的行為,開(kāi)心時(shí)的行為,傷心時(shí)的行為...
既然是解耦,那先把行為拿出來(lái)作為接口,在以具體的行為類(lèi)實(shí)現(xiàn)接口??纯丛趺窗研袨轭?lèi)和對(duì)象類(lèi)分開(kāi)。
實(shí)現(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 {}
這里把對(duì)象類(lèi)的 action
屬性拿掉,相應(yīng)的把 action
轉(zhuǎn)換為行為類(lèi)。行為類(lèi)實(shí)現(xiàn)了接口,其中定義了 manStatus()
和 womanStatus()
方法。
這樣我們拆成了兩個(gè)類(lèi),對(duì)象類(lèi)和行為類(lèi)。怎么把這兩個(gè)類(lèi)聯(lián)系起來(lái)呢?看代碼,男人類(lèi)和男人類(lèi)的行為,女人類(lèi)和女人類(lèi)的行為是有聯(lián)系的,初始化對(duì)象類(lèi)和行為類(lèi):
m := man{}s := sad{}// m.demo(s)
應(yīng)該通過(guò) demo
將對(duì)象和行為類(lèi)聯(lián)系起來(lái),然后調(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
作為對(duì)象類(lèi)的方法,在其中調(diào)用行為類(lèi)的方法。
至此,我們已經(jīng)實(shí)現(xiàn)了訪問(wèn)者模式。有人可能會(huì)問(wèn)了,我怎么還是沒(méi)看出來(lái)?我們?cè)诜治錾鲜龃a。
有兩個(gè)類(lèi),對(duì)象類(lèi)和行為類(lèi)。對(duì)象類(lèi)實(shí)現(xiàn) accept
方法,其是動(dòng)作的主體,將對(duì)象類(lèi)和行為類(lèi)關(guān)聯(lián)起來(lái)。行為類(lèi)根據(jù)不同對(duì)象實(shí)現(xiàn)行為方法,其是行為的主體,行為是建立在對(duì)象之上的。
基于上述分析,我們繼續(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)基本沒(méi)變,只是重命名了一下,更容易分清主體。
上述示例對(duì)于行為類(lèi)相比于對(duì)象類(lèi)是主的體驗(yàn)還不明顯,重新改寫(xiě)代碼:
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)}
改寫(xiě)后的代碼,行為類(lèi)會(huì)訪問(wèn)對(duì)象類(lèi)的 name
屬性,對(duì)于行為類(lèi)來(lái)說(shuō),對(duì)象類(lèi)就是數(shù)據(jù),是屬性。
基于此,畫(huà)出訪問(wèn)者設(shè)計(jì)模式的 UML 圖:
訪問(wèn)者模式在 GoF 合著的《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》中的定義是:
允許一個(gè)或多個(gè)操作應(yīng)用到一組對(duì)象上,解耦操作和對(duì)象本身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 和訪問(wèn)者模式前面介紹了訪問(wèn)者模式,從定義看訪問(wèn)者模式通過(guò)將一個(gè)或多個(gè)操作應(yīng)用到一組對(duì)象上,以實(shí)現(xiàn)對(duì)象和操作的解耦。這里需要重點(diǎn)關(guān)注的點(diǎn)是一組對(duì)象。
一組意味著對(duì)象具有相似性,且結(jié)構(gòu)是穩(wěn)定的。試想如果男人和女人類(lèi)中,女人沒(méi)有傷心時(shí)的行為,那就沒(méi)辦法將其歸為一組對(duì)象,或者需要實(shí)現(xiàn) fake 傷心以保持對(duì)象的相似性。
既然定義的是一組,當(dāng)然對(duì)象也可以是一個(gè)。假定對(duì)象是一個(gè),使用函數(shù) Visitor
改寫(xiě)上述訪問(wèn)者模式代碼:
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)}
這么改寫(xiě)在于簡(jiǎn)單,去掉類(lèi)取而代之的是函數(shù),用函數(shù)實(shí)現(xiàn)了具體的 Visitor
。當(dāng)然,這樣的 Visitor
只能處理一種操作。
類(lèi)似地,如果一組對(duì)象的行為是一樣的,也可以用函數(shù) Visitor
來(lái)實(shí)現(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如果操作作用于一個(gè)對(duì)象,可以用函數(shù) Visitor
來(lái)簡(jiǎn)化實(shí)現(xiàn)。如果多個(gè)操作嵌套的作用于對(duì)象上,那么可以使用嵌套 Visitor
實(shí)現(xiàn),其效果類(lèi)似于多個(gè)小應(yīng)用訪問(wèn)數(shù)據(jù)庫(kù)以實(shí)現(xiàn)某個(gè)功能。
代碼示例如下:
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)}
代碼有點(diǎn)長(zhǎng),其基本是 Kubernetes:kubectl
訪問(wèn)者模式的主體,把它看懂了,再去看 kubectl
的訪問(wèn)者模式實(shí)現(xiàn)就不難了。
首先,對(duì)象實(shí)現(xiàn)了 Visitor
(類(lèi)似于上例的 accept
),接受函數(shù) Visitor
,函數(shù) Visitor
訪問(wèn)對(duì)象操作:
func (m *man) Visit(fn VisitorFunc) error {...}
接著,將多個(gè)對(duì)象裝入 VisitorList
,且該 VisitorList
也實(shí)現(xiàn)了 Visitor
接口方法。這么做是為了遍歷訪問(wèn)每個(gè)對(duì)象:
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
操作對(duì)象之后對(duì)對(duì)象做一些其它操作,這里定義了 validationVisitor
用來(lái)驗(yàn)證對(duì)象的名字是否為空:
func (v validationVisitor) Visit(fn VisitorFunc) error {...}// mainvisitor = validationVisitor{visitor: visitor}visitor.Visit(happyVisitor)
通過(guò)層層嵌套 Visitor
實(shí)現(xiàn)對(duì)象的嵌套操作。
這些代碼了解了,再去看 Kubernetes:kubectl
應(yīng)該不難了,代碼在 這里。
優(yōu)點(diǎn)
訪問(wèn)者模式適用于對(duì)象復(fù)雜且具有較多操作的場(chǎng)景,使用訪問(wèn)者模式可解耦對(duì)象和操作,簡(jiǎn)化對(duì)象職責(zé)。訪問(wèn)者模式側(cè)重在訪問(wèn)者,對(duì)于訪問(wèn)者而言,多加幾個(gè)訪問(wèn)者操作不影響對(duì)象的實(shí)現(xiàn),符合開(kāi)閉原則。缺點(diǎn)
由于訪問(wèn)者模式解耦了對(duì)象和它的操作,對(duì)象的屬性暴露給訪問(wèn)者,打破了對(duì)象和操作的封裝。訪問(wèn)者模式對(duì)對(duì)象不友好,如果對(duì)象結(jié)構(gòu)不穩(wěn)定,很難使用訪問(wèn)者模式。同時(shí),如果要加入新對(duì)象,需要訪問(wèn)者接口,實(shí)現(xiàn)和對(duì)象都要改,不符合開(kāi)閉原則。4. 參考文章訪問(wèn)者模式GO 編程模式:K8S VISITOR 模式