7

手撸golang 行为型设计模式 访问者模式

 3 years ago
source link: https://studygolang.com/articles/33319
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

手撸golang 行为型设计模式 访问者模式

缘起

最近复习设计模式

拜读谭勇德的<<设计模式就该这样学>>

本系列笔记拟采用golang练习之

访问者模式

访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式,
指封装一些作用于某种数据结构中的各元素的操作,
可以在不改变数据结构的前提下定义作用于这些元素的新的操作,
属于行为型设计模式。

访问者模式主要适用于以下应用场景:
(1)数据结构稳定,作用于数据结构的操作经常变化的场景。
(2)需要数据结构与数据操作分离的场景。
(3)需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。

(摘自 谭勇德 <<设计模式就该这样学>>)

场景

  • 某订单管理系统, 需要按不同维度统计分析销售订单
  • 区域销售报表: 需按销售区域, 统计销售情况
  • 品类销售报表: 需根据不同产品, 统计销售情况
  • 根据 访问者模式 , 可将不同的报表, 设计为销售订单的访问者

设计

  • SaleOrder: 销售订单实体类
  • ISaleOrderService: 销售订单服务接口
  • ISaleOrderVisitor: 销售订单访问者
  • tMockSaleOrderService: 虚拟的销售订单服务, 实现ISaleOrderService接口
  • CityVisitor: 区域销售报表, 按城市汇总销售情况, 实现ISaleOrderVisitor接口
  • ProductVisitor: 品类销售报表, 按产品汇总销售情况, 实现ISaleOrderVisitor接口

单元测试

visitor_pattern_test.go

package behavioral_patterns

import (
    "learning/gooop/behavioral_patterns/visitor"
    "testing"
)

func Test_VisitorPattern(t *testing.T) {
    // prepare sale orders
    service := visitor.MockSaleOrderService
    _ = service.Save(visitor.NewSaleOrder(1, "张三", "广州", "电视", 10))
    _ = service.Save(visitor.NewSaleOrder(2, "李四", "深圳", "冰箱", 20))
    _ = service.Save(visitor.NewSaleOrder(3, "王五", "东莞", "空调", 30))
    _ = service.Save(visitor.NewSaleOrder(4, "张三三", "广州", "空调", 10))
    _ = service.Save(visitor.NewSaleOrder(5, "李四四", "深圳", "电视", 20))
    _ = service.Save(visitor.NewSaleOrder(6, "王五五", "东莞", "冰箱", 30))

    // test CityVisitor
    cv := visitor.NewCityVisitor()
    service.Visit(cv)
    cv.Report()

    // test ProductVisitor
    pv := visitor.NewProductVisitor()
    service.Visit(pv)
    pv.Report()
}

测试输出

$ go test -v visitor_pattern_test.go 
=== RUN   Test_VisitorPattern
city=东莞, sum=60
city=广州, sum=20
city=深圳, sum=40
product=空调, sum=40
product=电视, sum=30
product=冰箱, sum=50
--- PASS: Test_VisitorPattern (0.00s)
PASS
ok      command-line-arguments  0.002s

SaleOrder.go

销售订单实体类

package visitor


type SaleOrder struct {
    ID int
    Customer string
    City string
    Product string
    Quantity int
}

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

ISaleOrderService.go

销售订单服务接口

package visitor

type ISaleOrderService interface {
    Save(order *SaleOrder) error
    Visit(visitor ISaleOrderVisitor)
}

ISaleOrderVisitor.go

销售订单访问者

package visitor

type ISaleOrderVisitor interface {
    Visit(it *SaleOrder)
    Report()
}

tMockSaleOrderService.go

虚拟的销售订单服务, 实现ISaleOrderService接口

package visitor


type tMockSaleOrderService struct {
    orders map[int]*SaleOrder
}

func newMockSaleOrderService() ISaleOrderService {
    return &tMockSaleOrderService{
        orders: make(map[int]*SaleOrder, 0),
    }
}

func (me *tMockSaleOrderService) Save(it *SaleOrder) error {
    me.orders[it.ID] = it
    return nil
}

func (me *tMockSaleOrderService) Visit(visitor ISaleOrderVisitor) {
    for _,v := range me.orders {
        visitor.Visit(v)
    }
}

var MockSaleOrderService = newMockSaleOrderService()

CityVisitor.go

区域销售报表, 按城市汇总销售情况, 实现ISaleOrderVisitor接口

package visitor

import "fmt"

type CityVisitor struct {
    cities map[string]int
}

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

func (me *CityVisitor) Visit(it *SaleOrder) {
    n,ok := me.cities[it.City]
    if ok {
        me.cities[it.City] = n + it.Quantity
    } else {
        me.cities[it.City] = it.Quantity
    }
}

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

ProductVisitor.go

品类销售报表, 按产品汇总销售情况, 实现ISaleOrderVisitor接口

package visitor

import "fmt"

type ProductVisitor struct {
    products map[string]int
}

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

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

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

访问者模式小结

访问者模式的优点
(1)解耦了数据结构与数据操作,使得操作集合可以独立变化。
(2)可以通过扩展访问者角色,实现对数据集的不同操作,程序扩展性更好。
(3)元素具体类型并非单一,访问者均可操作。
(4)各角色职责分离,符合单一职责原则。

访问者模式的缺点
(1)无法增加元素类型:若系统数据结构对象易于变化,
        经常有新的数据对象增加进来,
    则访问者类必须增加对应元素类型的操作,违背了开闭原则。
(2)具体元素变更困难:具体元素增加属性、删除属性等操作, 
    会导致对应的访问者类需要进行相应的修改,
    尤其当有大量访问者类时,修改范围太大。
(3)违背依赖倒置原则:为了达到“区别对待”,
        访问者角色依赖的是具体元素类型,而不是抽象。
    
(摘自 谭勇德 <<设计模式就该这样学>>)

(end)

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK