手機版 | 網(wǎng)站導(dǎo)航
觀察家網(wǎng) > 宏觀 >

淺析訪問者模式

博客園 | 2023-07-31 05:39:57
0. 前言

在閱讀 Kubernetes: kubectl源碼時看到有關(guān)訪問者設(shè)計模式的運用。訪問者模式是行為型設(shè)計模式的一種,本篇文章將對訪問者模式做一個介紹。

1. 訪問者模式1.1 示例

首先,給出一個比較粗糙的示例。

實現(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方法,這不符合開閉原則。

1.2 解耦對象和行為

既然不符合開閉原則,那怎么樣才能解耦對象和行為,使得行為的變化不會影響到對象實現(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)用 smanStatus()方法完成關(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)用行為類的方法。

1.3 訪問者模式

至此,我們已經(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)該不難了,代碼在 這里。

3. 總結(jié)

優(yōu)點

訪問者模式適用于對象復(fù)雜且具有較多操作的場景,使用訪問者模式可解耦對象和操作,簡化對象職責(zé)。訪問者模式側(cè)重在訪問者,對于訪問者而言,多加幾個訪問者操作不影響對象的實現(xiàn),符合開閉原則。

缺點

由于訪問者模式解耦了對象和它的操作,對象的屬性暴露給訪問者,打破了對象和操作的封裝。訪問者模式對對象不友好,如果對象結(jié)構(gòu)不穩(wěn)定,很難使用訪問者模式。同時,如果要加入新對象,需要訪問者接口,實現(xiàn)和對象都要改,不符合開閉原則。4. 參考文章

訪問者模式GO 編程模式:K8S VISITOR 模式

標(biāo)簽:

  • 標(biāo)簽:中國觀察家網(wǎng),商業(yè)門戶網(wǎng)站,新聞,專題,財經(jīng),新媒體,焦點,排行,教育,熱點,行業(yè),消費,互聯(lián)網(wǎng),科技,國際,文化,時事,社會,國內(nèi),健康,產(chǎn)業(yè)資訊,房產(chǎn),體育。

相關(guān)推薦