缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之

访问者模式

  1. 访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式,
  2. 指封装一些作用于某种数据结构中的各元素的操作,
  3. 可以在不改变数据结构的前提下定义作用于这些元素的新的操作,
  4. 属于行为型设计模式。
  5. 访问者模式主要适用于以下应用场景:
  6. 1)数据结构稳定,作用于数据结构的操作经常变化的场景。
  7. 2)需要数据结构与数据操作分离的场景。
  8. 3)需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。
  9. (摘自 谭勇德 <<设计模式就该这样学>>)

场景

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

设计

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

单元测试

visitor_pattern_test.go

  1. package behavioral_patterns
  2. import (
  3. "learning/gooop/behavioral_patterns/visitor"
  4. "testing"
  5. )
  6. func Test_VisitorPattern(t *testing.T) {
  7. // prepare sale orders
  8. service := visitor.MockSaleOrderService
  9. _ = service.Save(visitor.NewSaleOrder(1, "张三", "广州", "电视", 10))
  10. _ = service.Save(visitor.NewSaleOrder(2, "李四", "深圳", "冰箱", 20))
  11. _ = service.Save(visitor.NewSaleOrder(3, "王五", "东莞", "空调", 30))
  12. _ = service.Save(visitor.NewSaleOrder(4, "张三三", "广州", "空调", 10))
  13. _ = service.Save(visitor.NewSaleOrder(5, "李四四", "深圳", "电视", 20))
  14. _ = service.Save(visitor.NewSaleOrder(6, "王五五", "东莞", "冰箱", 30))
  15. // test CityVisitor
  16. cv := visitor.NewCityVisitor()
  17. service.Visit(cv)
  18. cv.Report()
  19. // test ProductVisitor
  20. pv := visitor.NewProductVisitor()
  21. service.Visit(pv)
  22. pv.Report()
  23. }

测试输出

  1. $ go test -v visitor_pattern_test.go
  2. === RUN Test_VisitorPattern
  3. city=东莞, sum=60
  4. city=广州, sum=20
  5. city=深圳, sum=40
  6. product=空调, sum=40
  7. product=电视, sum=30
  8. product=冰箱, sum=50
  9. --- PASS: Test_VisitorPattern (0.00s)
  10. PASS
  11. ok command-line-arguments 0.002s

SaleOrder.go

销售订单实体类

  1. package visitor
  2. type SaleOrder struct {
  3. ID int
  4. Customer string
  5. City string
  6. Product string
  7. Quantity int
  8. }
  9. func NewSaleOrder(id int, customer string, city string, product string, quantity int) *SaleOrder {
  10. return &SaleOrder{
  11. id, customer,city,product,quantity,
  12. }
  13. }

ISaleOrderService.go

销售订单服务接口

  1. package visitor
  2. type ISaleOrderService interface {
  3. Save(order *SaleOrder) error
  4. Visit(visitor ISaleOrderVisitor)
  5. }

ISaleOrderVisitor.go

销售订单访问者

  1. package visitor
  2. type ISaleOrderVisitor interface {
  3. Visit(it *SaleOrder)
  4. Report()
  5. }

tMockSaleOrderService.go

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

  1. package visitor
  2. type tMockSaleOrderService struct {
  3. orders map[int]*SaleOrder
  4. }
  5. func newMockSaleOrderService() ISaleOrderService {
  6. return &tMockSaleOrderService{
  7. orders: make(map[int]*SaleOrder, 0),
  8. }
  9. }
  10. func (me *tMockSaleOrderService) Save(it *SaleOrder) error {
  11. me.orders[it.ID] = it
  12. return nil
  13. }
  14. func (me *tMockSaleOrderService) Visit(visitor ISaleOrderVisitor) {
  15. for _,v := range me.orders {
  16. visitor.Visit(v)
  17. }
  18. }
  19. var MockSaleOrderService = newMockSaleOrderService()

CityVisitor.go

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

  1. package visitor
  2. import "fmt"
  3. type CityVisitor struct {
  4. cities map[string]int
  5. }
  6. func NewCityVisitor() ISaleOrderVisitor {
  7. return &CityVisitor{
  8. cities: make(map[string]int,0),
  9. }
  10. }
  11. func (me *CityVisitor) Visit(it *SaleOrder) {
  12. n,ok := me.cities[it.City]
  13. if ok {
  14. me.cities[it.City] = n + it.Quantity
  15. } else {
  16. me.cities[it.City] = it.Quantity
  17. }
  18. }
  19. func (me *CityVisitor) Report() {
  20. for k,v := range me.cities {
  21. fmt.Printf("city=%s, sum=%v\n", k, v)
  22. }
  23. }

ProductVisitor.go

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

  1. package visitor
  2. import "fmt"
  3. type ProductVisitor struct {
  4. products map[string]int
  5. }
  6. func NewProductVisitor() ISaleOrderVisitor {
  7. return &ProductVisitor{
  8. products: make(map[string]int,0),
  9. }
  10. }
  11. func (me *ProductVisitor) Visit(it *SaleOrder) {
  12. n,ok := me.products[it.Product]
  13. if ok {
  14. me.products[it.Product] = n + it.Quantity
  15. } else {
  16. me.products[it.Product] = it.Quantity
  17. }
  18. }
  19. func (me *ProductVisitor) Report() {
  20. for k,v := range me.products {
  21. fmt.Printf("product=%s, sum=%v\n", k, v)
  22. }
  23. }

访问者模式小结

  1. 访问者模式的优点
  2. 1)解耦了数据结构与数据操作,使得操作集合可以独立变化。
  3. 2)可以通过扩展访问者角色,实现对数据集的不同操作,程序扩展性更好。
  4. 3)元素具体类型并非单一,访问者均可操作。
  5. 4)各角色职责分离,符合单一职责原则。
  6. 访问者模式的缺点
  7. 1)无法增加元素类型:若系统数据结构对象易于变化,
  8. 经常有新的数据对象增加进来,
  9. 则访问者类必须增加对应元素类型的操作,违背了开闭原则。
  10. 2)具体元素变更困难:具体元素增加属性、删除属性等操作,
  11. 会导致对应的访问者类需要进行相应的修改,
  12. 尤其当有大量访问者类时,修改范围太大。
  13. 3)违背依赖倒置原则:为了达到“区别对待”,
  14. 访问者角色依赖的是具体元素类型,而不是抽象。
  15. (摘自 谭勇德 <<设计模式就该这样学>>)

(end)