訪問者模式通過雙重分發(fā)解耦數(shù)據(jù)結(jié)構(gòu)與操作。其核心在于:1. 定義 element 接口,包含 accept 方法;2. 定義 visitor 接口,包含多個(gè) visit 方法;3. 具體 element 實(shí)現(xiàn) accept 并調(diào)用對(duì)應(yīng) visit 方法。在 golang 中,雖無繼承機(jī)制,但通過接口實(shí)現(xiàn)雙重分發(fā),即運(yùn)行時(shí)根據(jù) element 和 visitor 的實(shí)際類型決定調(diào)用的具體方法。示例中 book 和 dvd 實(shí)現(xiàn) accept,并由 pricevisitor 統(tǒng)一處理打印價(jià)格。該模式要求清晰設(shè)計(jì)接口,新增 element 需同步更新所有 visitor 實(shí)現(xiàn),適合結(jié)構(gòu)穩(wěn)定、需統(tǒng)一處理的場(chǎng)景,且可通過抽象工廠或依賴注入提升擴(kuò)展性。
訪問者模式的核心在于解耦數(shù)據(jù)結(jié)構(gòu)和作用于其上的操作。在 golang 中,雖然沒有繼承機(jī)制,但通過接口(Interface)可以巧妙地實(shí)現(xiàn)“雙重分發(fā)”,從而模擬訪問者模式的行為。
這篇文章就來聊聊如何用 Golang 實(shí)現(xiàn)訪問者模式,重點(diǎn)是基于接口的雙重分發(fā)技巧。
什么是訪問者模式?
訪問者模式允許你定義一組操作,這些操作可以作用于某個(gè)對(duì)象結(jié)構(gòu)中的不同元素,而無需修改這些元素本身的類。這種模式特別適合需要對(duì)復(fù)雜對(duì)象結(jié)構(gòu)進(jìn)行統(tǒng)一處理的場(chǎng)景。
立即學(xué)習(xí)“go語(yǔ)言免費(fèi)學(xué)習(xí)筆記(深入)”;
核心概念有兩個(gè):
- Element:被訪問的對(duì)象,它提供一個(gè)接受訪問者的方法(比如 Accept(v Visitor))。
- Visitor:訪問者接口,定義一系列 VisitXxx 方法,對(duì)應(yīng)不同的 Element 類型。
關(guān)鍵點(diǎn)在于:訪問者調(diào)用元素的 Accept 方法,而元素又反過來調(diào)用訪問者的 Visit 方法,這就是所謂的“雙重分發(fā)”。
如何用 Golang 接口實(shí)現(xiàn)雙重分發(fā)
Golang 沒有類和繼承,但可以通過接口和函數(shù)組合實(shí)現(xiàn)類似效果。下面是具體步驟:
- 定義 Visitor 接口,里面包含多個(gè) Visit 方法,分別對(duì)應(yīng)不同類型的 Element。
- 定義 Element 接口,必須有一個(gè) Accept 方法,參數(shù)為 Visitor。
- 各個(gè)具體的 Element 結(jié)構(gòu)體實(shí)現(xiàn) Accept 方法,并在里面調(diào)用對(duì)應(yīng)的 Visit 方法。
舉個(gè)簡(jiǎn)單例子,假設(shè)我們有兩個(gè)元素類型:Book 和 DVD,我們要實(shí)現(xiàn)打印價(jià)格的操作。
type Visitor interface { VisitBook(book *Book) VisitDVD(dvd *DVD) } type Element interface { Accept(visitor Visitor) }
具體元素:
type Book struct { Price float64 } func (b *Book) Accept(visitor Visitor) { visitor.VisitBook(b) } type DVD struct { Price float64 } func (d *DVD) Accept(visitor Visitor) { visitor.VisitDVD(d) }
然后定義一個(gè)打印價(jià)格的訪問者:
type PriceVisitor struct{} func (v *PriceVisitor) VisitBook(book *Book) { fmt.Println("Book price:", book.Price) } func (v *PriceVisitor) VisitDVD(dvd *DVD) { fmt.Println("DVD price:", dvd.Price) }
這樣就可以統(tǒng)一處理了:
elements := []Element{ &Book{Price: 59.9}, &DVD{Price: 39.9}, } visitor := &PriceVisitor{} for _, e := range elements { e.Accept(visitor) }
為什么說這是“雙重分發(fā)”?
雙重分派的意思是:方法調(diào)用不是只看調(diào)用者的靜態(tài)類型,而是根據(jù)運(yùn)行時(shí)兩個(gè)對(duì)象的實(shí)際類型決定調(diào)用哪個(gè)方法。
在上面的例子中:
- 第一次分發(fā)發(fā)生在調(diào)用 e.Accept(visitor) 的時(shí)候,Go 根據(jù) e 的實(shí)際類型調(diào)用對(duì)應(yīng)結(jié)構(gòu)體的 Accept 方法。
- 第二次分發(fā)發(fā)生在 visitor.VisitXXX(e) 這一步,根據(jù) visitor 的類型和 e 的類型,最終調(diào)用了正確的 Visit 方法。
雖然 Go 是靜態(tài)語(yǔ)言,也沒有重載,但通過這種方式實(shí)現(xiàn)了類似動(dòng)態(tài)雙分發(fā)的效果。
使用訪問者模式的一些注意事項(xiàng)
- 接口設(shè)計(jì)要清晰,每個(gè) Visit 方法應(yīng)該對(duì)應(yīng)一種 Element 類型。
- 如果新增 Element 類型,就必須同步更新所有 Visitor 實(shí)現(xiàn),否則會(huì)遺漏邏輯。
- 不適合頻繁變動(dòng)的結(jié)構(gòu),維護(hù)成本較高。
- 可以配合抽象工廠或依賴注入使用,提升擴(kuò)展性。
如果你發(fā)現(xiàn) Visitor 接口變得越來越大,可能說明職責(zé)劃分有問題,可以考慮拆分訪問者功能。
基本上就這些。訪問者模式在 Golang 中雖然不如 Java 那樣自然,但通過接口和雙重分發(fā)機(jī)制,依然可以優(yōu)雅地實(shí)現(xiàn)。關(guān)鍵是理解 Accept 和 Visit 的調(diào)用關(guān)系,以及 Visitor 接口的設(shè)計(jì)方式。