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

淺析訪問者模式

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

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

1. 訪問者模式1.1 示例

首先,給出一個(gè)比較粗糙的示例。

實(shí)現(xiàn)程序,功能如下:


(相關(guān)資料圖)

當(dāng)男人成功時(shí),顯示我有一個(gè)好老婆;當(dāng)女人成功時(shí),顯示我有一個(gè)有愛的丈夫;當(dāng)男人開心時(shí),顯示我有一個(gè)玩具;當(dāng)女人開心時(shí),顯示我有一個(gè)有愛的丈夫;當(dāng)男人傷心時(shí),顯示我丟了玩具;當(dāng)女人傷心時(shí),顯示我丟了有愛的丈夫;

基于上述描述,實(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)了想要的功能??梢钥闯?,男人和女人類都具有 action且行為不一樣。

那么,如果增加 action,比如當(dāng)男人戀愛時(shí),顯示我終于脫單了;當(dāng)女人戀愛時(shí),顯示終于有人愛我了;就需要更新男人和女人類的 status方法,這不符合開閉原則。

1.2 解耦對(duì)象和行為

既然不符合開閉原則,那怎么樣才能解耦對(duì)象和行為,使得行為的變化不會(huì)影響到對(duì)象實(shí)現(xiàn)呢?

進(jìn)一步看上述實(shí)現(xiàn),男人和女人類是行為的主體,行為有成功時(shí)的行為,開心時(shí)的行為,傷心時(shí)的行為...

既然是解耦,那先把行為拿出來作為接口,在以具體的行為類實(shí)現(xiàn)接口。看看怎么把行為類和對(duì)象類分開。

實(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ì)象類的 action屬性拿掉,相應(yīng)的把 action轉(zhuǎn)換為行為類。行為類實(shí)現(xiàn)了接口,其中定義了 manStatus()womanStatus()方法。

這樣我們拆成了兩個(gè)類,對(duì)象類和行為類。怎么把這兩個(gè)類聯(lián)系起來呢?看代碼,男人類和男人類的行為,女人類和女人類的行為是有聯(lián)系的,初始化對(duì)象類和行為類:

m := man{}s := sad{}// m.demo(s)

應(yīng)該通過 demo將對(duì)象和行為類聯(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作為對(duì)象類的方法,在其中調(diào)用行為類的方法。

1.3 訪問者模式

至此,我們已經(jīng)實(shí)現(xiàn)了訪問者模式。有人可能會(huì)問了,我怎么還是沒看出來?我們?cè)诜治錾鲜龃a。

有兩個(gè)類,對(duì)象類和行為類。對(duì)象類實(shí)現(xiàn) accept方法,其是動(dòng)作的主體,將對(duì)象類和行為類關(guān)聯(lián)起來。行為類根據(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)基本沒變,只是重命名了一下,更容易分清主體。

上述示例對(duì)于行為類相比于對(duì)象類是主的體驗(yàn)還不明顯,重新改寫代碼:

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)}

改寫后的代碼,行為類會(huì)訪問對(duì)象類的 name屬性,對(duì)于行為類來說,對(duì)象類就是數(shù)據(jù),是屬性。

基于此,畫出訪問者設(shè)計(jì)模式的 UML 圖:

訪問者模式在 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 和訪問者模式

前面介紹了訪問者模式,從定義看訪問者模式通過將一個(gè)或多個(gè)操作應(yīng)用到一組對(duì)象上,以實(shí)現(xiàn)對(duì)象和操作的解耦。這里需要重點(diǎn)關(guān)注的點(diǎn)是一組對(duì)象。

一組意味著對(duì)象具有相似性,且結(jié)構(gòu)是穩(wěn)定的。試想如果男人和女人類中,女人沒有傷心時(shí)的行為,那就沒辦法將其歸為一組對(duì)象,或者需要實(shí)現(xiàn) fake 傷心以保持對(duì)象的相似性。

既然定義的是一組,當(dāng)然對(duì)象也可以是一個(gè)。假定對(duì)象是一個(gè),使用函數(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)}

這么改寫在于簡(jiǎn)單,去掉類取而代之的是函數(shù),用函數(shù)實(shí)現(xiàn)了具體的 Visitor。當(dāng)然,這樣的 Visitor只能處理一種操作。

類似地,如果一組對(duì)象的行為是一樣的,也可以用函數(shù) Visitor來實(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來簡(jiǎn)化實(shí)現(xiàn)。如果多個(gè)操作嵌套的作用于對(duì)象上,那么可以使用嵌套 Visitor實(shí)現(xiàn),其效果類似于多個(gè)小應(yīng)用訪問數(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訪問者模式的主體,把它看懂了,再去看 kubectl的訪問者模式實(shí)現(xiàn)就不難了。

首先,對(duì)象實(shí)現(xiàn)了 Visitor(類似于上例的 accept),接受函數(shù) Visitor,函數(shù) Visitor訪問對(duì)象操作:

func (m *man) Visit(fn VisitorFunc) error {...}

接著,將多個(gè)對(duì)象裝入 VisitorList,且該 VisitorList也實(shí)現(xiàn)了 Visitor接口方法。這么做是為了遍歷訪問每個(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用來驗(yàn)證對(duì)象的名字是否為空:

func (v validationVisitor) Visit(fn VisitorFunc) error {...}// mainvisitor = validationVisitor{visitor: visitor}visitor.Visit(happyVisitor)

通過層層嵌套 Visitor實(shí)現(xiàn)對(duì)象的嵌套操作。

這些代碼了解了,再去看 Kubernetes:kubectl應(yīng)該不難了,代碼在 這里。

3. 總結(jié)

優(yōu)點(diǎn)

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

缺點(diǎn)

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

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

標(biāo)簽:

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

相關(guān)推薦