Go 設計模式 -- 訪客模式

訪客模式也叫訪問者模式(Visitor Pattern)是一種將數據結構對象與數據操作分離的設計模式,可以在不改變數據結構對象類結構的前提下定義作用於這些對象的新的操作, 屬於行爲型設計模式。

訪問者模式主要適用於以下應用場景:

  1. 數據結構穩定,作用於數據結構的操作經常變化的場景。

  2. 需要數據結構與數據操作分離的場景。

  3. 需要對不同數據類型(元素)進行操作,而不使用分支判斷具體類型的場景。

訪客模式怎麼工作?

訪問者模式通過將算法與對象結構分離來工作,這裏說的算法指的是對對象的操作。爲此,我們需要定義了一個表示算法的接口 --Visitor。該接口將爲對象結構中的每個類(一般稱爲元素類)提供一個方法。每個方法都將元素類的一個實例作爲參數。表示對象結構的所有元素類也會實現一個 Element 接口,該接口定義了接受訪問者的方法 Accpet。此方法將訪問者接口的實現作爲參數。當 Accpet 方法被調用時,訪問者實例對應的方法就會被調用,通過訪問者完成對元素類實例的操作。

下面我們看一下訪問者模式的類結構。

訪客模式結構

訪問者的類結構可以用下面的 UML 類圖來表示:

訪客模式代碼示例

在這個用訪客模式實現不同維度的訂單統計的例子裏,假設我們建設了一個訂單管理系統, 現在系統中要求能按照不同維度統計分析銷售訂單

以後還有可能增加其他維度的銷售統計報表,針對這個需求我們可以根據訪問者模式, 可將不同的報表, 設計爲訂單的訪問者。 首先定義訂單實體和它要實現的 Element 接口

"本文使用的完整可運行源碼
去公衆號「網管叨bi叨」發送【設計模式】即可領取"
// 訂單服務接口
type IOrderService interface {
 Save(order *Order) error
 // 有的教程裏把接收 visitor 實現的方法名定義成 Accept
 Accept(visitor IOrderVisitor)
}


// 訂單實體類,實現IOrderService 接口
type Order struct {
 ID int
 Customer string
 City string
 Product string
 Quantity int
}

func (mo *OrderService) Save(o *Order) error {
 mo.orders[o.ID] = o
 return nil
}

func (mo *OrderService) Accept(visitor IOrderVisitor) {
 for _, v := range mo.orders {
  visitor.Visit(v)
 }
}

func NewOrder(id int, customer string, city string, product string, quantity int) *Order {
 return &Order{
  id, customer,city,product,quantity,
 }
}

接下來定義生成各種銷售報表的訪客類,以及它們實現的訪客接口

"本文使用的完整可運行源碼
去公衆號「網管叨bi叨」發送【設計模式】即可領取"
type IOrderVisitor interface {
 // 這裏參數不能定義成 IOrderService
 Visit(order *Order)
 Report()
}

type CityVisitor struct {
 cities map[string]int
}

func (cv *CityVisitor) Visit(o *Order) {
 n, ok := cv.cities[o.City]
 if ok {
  cv.cities[o.City] = n + o.Quantity
 } else {
  cv.cities[o.City] = o.Quantity
 }
}

func (cv *CityVisitor) Report() {
 for k,v := range cv.cities {
  fmt.Printf("city=%s, sum=%v\n", k, v)
 }
}

func NewCityVisitor() IOrderVisitor {
 return &CityVisitor{
  cities: make(map[string]int, 0),
 }
}

// 品類銷售報表, 按產品彙總銷售情況, 實現ISaleOrderVisitor接口
type ProductVisitor struct {
 products map[string]int
}

func (pv *ProductVisitor) Visit(it *Order) {
 n,ok := pv.products[it.Product]
 if ok {
  pv.products[it.Product] = n + it.Quantity
 } else {
  pv.products[it.Product] = it.Quantity
 }
}

func (pv *ProductVisitor) Report() {
 for k,v := range pv.products {
  fmt.Printf("product=%s, sum=%v\n", k, v)
 }
}

func NewProductVisitor() IOrderVisitor {
 return &ProductVisitor{
  products: make(map[string]int,0),
 }
}

最後我們嘗試使用 Vistor 生成各種銷售報表

func main() {
 orderService := NewOrderService()
 orderService.Save(NewOrder(1, "張三""廣州""電視", 10))
 orderService.Save(NewOrder(2, "李四""深圳""冰箱", 20))
 orderService.Save(NewOrder(3, "王五""東莞""空調", 30))
 orderService.Save(NewOrder(4, "張三三""廣州""空調", 10))
 orderService.Save(NewOrder(5, "李四四""深圳""電視", 20))
 orderService.Save(NewOrder(6, "王五五""東莞""冰箱", 30))

 cv := NewCityVisitor()
 orderService.Accept(cv)
 cv.Report()

 pv := NewProductVisitor()
 orderService.Accept(pv)
 pv.Report()
}

本文的完整源碼,已經同步收錄到我整理的電子教程裏啦,可向我的公衆號「網管叨 bi 叨」發送關鍵字【設計模式】領取。

公衆號「網管叨 bi 叨」發送關鍵字【設計模式】領取。

總結

訪客模式有如下優點

與此同時它也有以下缺點

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/qsw89qI8DOXyb4C1XI5QtA