手撸golang 行为型设计模式 访问者模式
2010 年 8 月 28 日
手撸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)
有疑问加站长微信联系(非本文作者)