访问者模式难理解、难实现,应用它会导致代码的可读性、可维护性变差,所以,访问者模式在实际的软件开发中很少被用到,在没有特别必要的情况下,建议不要使用访问者模式。
1. 访问者模式的原理
访问者模式的英文翻译是 Visitor Design Pattern。允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure.
一般来说,访问者模式针对的是一组类型不同的对象,尽管这组对象的类型是不同的,但是,它们继承相同的父类或者实现相同的接口。在不同的应用场景下,需要对这组对象进行一系列不相关的业务操作,但为了避免不断添加功能导致类不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类中。
2. 访问者模式的难点
对于访问者模式,学习的主要难点在代码实现。而代码实现比较复杂的主要原因是,函数重载在大部分面向对象编程语言中是静态绑定的。也就是说,调用类的哪个重载函数,是在编译期间,由参数的声明类型决定的,而非运行时,根据参数的实际类型决定的。
正是因为代码实现难理解,所以,在项目中应用这种模式,会导致代码的可读性比较差。如果你的同事不了解这种设计模式,可能就会读不懂、维护不了你写的代码。所以,除非不得已,不要使用这种模式。
如果不使用访问者模式,可以使用工厂模式,在 map 中保存 type 和具体子类实例的映射,在使用的时候,根据 type 的不同调用不同子类的方法(动态绑定)。
如果操作功能并不是非常多,只有几个而已,那更推荐使用工厂模式的实现方式,毕竟代码更加清晰、易懂。相反,如果操作功能非常多,比如有十几个,那更推荐使用访问者模式,因为访问者模式需要定义的类要比工厂模式的实现方式少很多,类太多也会影响到代码的可维护性。
3. 为什么支持双分派的语言不需要访问者模式
- Single Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定。
- Double Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。
目前主流的语言都只支持 Single Dispatch,不支持 Double Dispathch。
4. 访问者模式的代码实现
type shape interface {
getType() string
accept(visitor)
}
type square struct {
side int
}
func (s *square) accept(v visitor) {
v.visitForSquare(s)
}
func (s *square) getType() string {
return "Square"
}
type circle struct {
radius int
}
func (c *circle) accept(v visitor) {
v.visitForCircle(c)
}
func (c *circle) getType() string {
return "Circle"
}
type rectangle struct {
length int
width int
}
func (t *rectangle) accept(v visitor) {
v.visitFoRectangle(t)
}
func (t *rectangle) getType() string {
return "rectangle"
}
type visitor interface {
// 定义多个抽象方法,是因为 golang 不支持方法重载,简单点说,就是不支持方法同名
visitForSquare(*square)
visitForCircle(*circle)
visitFoRectangle(*rectangle)
}
type areaCalculator struct {
}
func (a *areaCalculator) visitForSquare(s *square) {
//Calculate area for square. After calculating the area assign in to the area instance variable
area := s.side * s.side
fmt.Printf("Calculating area for square, side: %d, area: %d\n", s.side, area)
}
func (a *areaCalculator) visitForCircle(s *circle) {
//Calculate are for circle. After calculating the area assign in to the area instance variable
radius := float64(s.radius)
area := math.Pi * radius * radius
fmt.Printf("Calculating area for circle radisu: %.3f, area: %.3f\n", radius, area)
}
func (a *areaCalculator) visitFoRectangle(s *rectangle) {
//Calculate are for rectangle. After calculating the area assign in to the area instance variable
area := s.length * s.width
fmt.Printf("Calculating area for rectangle, length: %d, width: %d, area: %d\n", s.length, s.width, area)
}
type middleCoordinates struct {
}
func (a *middleCoordinates) visitForSquare(s *square) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for square")
}
func (a *middleCoordinates) visitForCircle(c *circle) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for circle")
}
func (a *middleCoordinates) visitFoRectangle(t *rectangle) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for rectangle")
}
// 客户端代码实现
func TestVisitor(t *testing.T) {
square := &square{side: 2}
circle := &circle{radius: 2}
rectangle := &rectangle{
length: 2,
width: 3,
}
var visitor visitor
visitor = &areaCalculator{}
square.accept(visitor)
circle.accept(visitor)
rectangle.accept(visitor)
visitor = &middleCoordinates{}
square.accept(visitor)
circle.accept(visitor)
rectangle.accept(visitor)
}