访问者模式
简介
访问者模式(Visitor Pattern) 是一种行为型设计模式,允许在不修改对象结构的前提下,定义新操作作用于对象集合中的各个元素。它的核心思想是将算法与对象结构分离,通过“双分派”(Double Dispatch)机制实现动态绑定。
关键角色:
- Visitor:定义访问具体元素的接口(如
VisitCircle
,VisitRectangle
) - ConcreteVisitor:实现具体的访问逻辑(如面积计算、序列化)
- Element:定义接受访问者的接口(
Accept(Visitor)
) - ConcreteElement:具体元素实现
Accept
方法
常见使用场景
- 复杂对象结构的统一处理
- 抽象语法树(AST)的遍历(编译器中的类型检查、代码优化)
- 文件系统目录树的统计/备份操作
- 动态扩展数据结构的操作
- 报表导出(支持 CSV/JSON/XML 多种格式)
- 游戏场景中不同物体的碰撞检测逻辑
- 避免污染对象模型
- 当对象结构稳定但需要频繁添加新操作时
优点
- 开闭原则:新增访问者无需修改现有代码
- 集中算法逻辑:相关操作聚合在同一个访问者中
- 跨对象层级操作:可处理异构对象集合(如不同图形类型)
缺点
- 破坏封装性:访问者可能需要访问元素的私有状态
- 增加新元素类型困难:每增加一个具体元素类,都需要修改具体访问者类
- 复杂化代码:对简单场景可能过度设计
示例代码
计算周长
假设现在有一个代码库,这个库包含不同的形状类,比如正方形类、圆形类、矩形类等,上述每个形状类都实现了通用形状接口。现在需要给每个形状类增加一个GetPerimeter()
方法来计算周长,可以在每个形状类中进行实现,但这样修改成本比较高。用访问者模式也能解决这个问题。
Go
package visitor
import (
"fmt"
"math"
)
// 访问者接口
type Visitor interface {
VisitForSquare(*Square)
VisitForCircle(*Circle)
VisitForRectangle(*Rectangle)
}
// 元素接口: 形状接口
type ShapeElement 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 float32
}
func (c *Circle) Accept(v Visitor) {
v.VisitForCircle(c)
}
func (c *Circle) GetType() string {
return "Circle"
}
// 具体元素类: 矩形类
type Rectangle struct {
Length int
Width int
}
func (r *Rectangle) Accept(v Visitor) {
v.VisitForRectangle(r)
}
func (r *Rectangle) GetType() string {
return "Rectangle"
}
// 具体访问者类 PerimeterCalculator, 周长计算
type PerimeterCalculator struct {
// perimeter float32
}
func (a *PerimeterCalculator) VisitForSquare(s *Square) {
// var perimeter float32
perimeter := float32(s.Side * 4)
fmt.Printf("Perimeter of square is %.4f\n", perimeter)
}
func (a *PerimeterCalculator) VisitForRectangle(r *Rectangle) {
// var perimeter float32
perimeter := float32(r.Length*2 + r.Width*2)
fmt.Printf("Perimeter of rectangle is %.4f\n", perimeter)
}
func (a *PerimeterCalculator) VisitForCircle(c *Circle) {
// var perimeter float32
perimeter := float32(math.Pi * 2 * c.Radius)
fmt.Printf("Perimeter of circle is %.4f\n", perimeter)
}
client
package visitor
import "testing"
func TestVisitor(t *testing.T) {
squre := &Square{Side: 2}
circle := &Circle{Radius: 3}
rectangle := &Rectangle{Length: 4, Width: 5}
perimeter := &PerimeterCalculator{}
squre.Accept(perimeter)
circle.Accept(perimeter)
rectangle.Accept(perimeter)
}
Python
from abc import ABC, abstractmethod
# 访问者接口
class Visitor(ABC):
@abstractmethod
def visit_square(self, square):
pass
@abstractmethod
def visit_circle(self, circle):
pass
@abstractmethod
def visit_rectangle(self, rectangle):
pass
# 具体访问者 - 周长计算器
class PerimeterCalculator(Visitor):
def visit_square(self, square):
perimeter = square.side * 4
print(f"Perimeter of square is {perimeter:.4f}")
def visit_circle(self, circle):
perimeter = 2 * 3.1415926535 * circle.radius
print(f"Perimeter of circle is {perimeter:.4f}")
def visit_rectangle(self, rectangle):
perimeter = 2 * (rectangle.length + rectangle.width)
print(f"Perimeter of rectangle is {perimeter:.4f}")
# 元素接口
class ShapeElement(ABC):
@abstractmethod
def accept(self, visitor):
pass
@abstractmethod
def get_type(self):
pass
# 正方形
class Square(ShapeElement):
def __init__(self, side):
self.side = side
def accept(self, visitor):
visitor.visit_square(self)
def get_type(self):
return "Square"
# 圆形
class Circle(ShapeElement):
def __init__(self, radius):
self.radius = radius
def accept(self, visitor):
visitor.visit_circle(self)
def get_type(self):
return "Circle"
# 矩形
class Rectangle(ShapeElement):
def __init__(self, length, width):
self.length = length
self.width = width
def accept(self, visitor):
visitor.visit_rectangle(self)
def get_type(self):
return "Rectangle"
if __name__ == "__main__":
shapes = [
Square(5),
Circle(3),
Rectangle(4, 6)
]
calculator = PerimeterCalculator()
for shape in shapes:
shape.accept(calculator)