Skip to content

访问者模式

约 790 字大约 3 分钟

设计模式

2018-05-05

什么是访问者模式?

Visitor(访问者)模式 是一种行为型设计模式。

它允许在不修改现有对象结构的前提下,为对象结构中的元素添加新的操作。

核心思想是将数据操作与数据结构分离,通过“访问者”对象实现对不同元素的操作扩展。

访问者模式主要由以下部分组成:

Visitor(访问者)

声明访问具体元素的方法(visitElementA, visitElementB)。

ConcreteVisitor(具体访问者)

实现访问者接口,定义对元素的具体操作逻辑。

Element(元素)

定义 accept(visitor) 方法,接收访问者对象。

ConcreteElement(具体元素)

实现 accept() 方法,调用访问者的对应方法。

ObjectStructure(对象结构)

维护元素集合,提供遍历接口供访问者操作。

实现访问者模式

// 1. 定义元素接口
class Shape {
  accept(visitor) {
    throw new Error('Method \'accept()\' must be implemented.')
  }
}

// 2. 具体元素:圆形
class Circle extends Shape {
  constructor(radius) {
    super()
    this.radius = radius
  }

  accept(visitor) {
    visitor.visitCircle(this) // 将自身传递给访问者
  }
}

// 3. 具体元素:矩形
class Rectangle extends Shape {
  constructor(width, height) {
    super()
    this.width = width
    this.height = height
  }

  accept(visitor) {
    visitor.visitRectangle(this)
  }
}

// 4. 访问者接口
class Visitor {
  visitCircle(circle) {}
  visitRectangle(rectangle) {}
}

// 5. 具体访问者:面积计算器
class AreaCalculator extends Visitor {
  visitCircle(circle) {
    const area = Math.PI * circle.radius ** 2
    console.log(`Circle area: ${area.toFixed(2)}`)
  }

  visitRectangle(rectangle) {
    const area = rectangle.width * rectangle.height
    console.log(`Rectangle area: ${area}`)
  }
}

// 6. 具体访问者:周长计算器
class PerimeterCalculator extends Visitor {
  visitCircle(circle) {
    const perimeter = 2 * Math.PI * circle.radius
    console.log(`Circle perimeter: ${perimeter.toFixed(2)}`)
  }

  visitRectangle(rectangle) {
    const perimeter = 2 * (rectangle.width + rectangle.height)
    console.log(`Rectangle perimeter: ${perimeter}`)
  }
}

// 7. 对象结构(管理元素集合)
class Drawing {
  constructor() {
    this.shapes = []
  }

  add(shape) {
    this.shapes.push(shape)
  }

  accept(visitor) {
    this.shapes.forEach(shape => shape.accept(visitor))
  }
}

// 客户端代码
const drawing = new Drawing()
drawing.add(new Circle(5))
drawing.add(new Rectangle(4, 6))

const areaCalculator = new AreaCalculator()
const perimeterCalculator = new PerimeterCalculator()

console.log('--- Area Calculation ---')
drawing.accept(areaCalculator)

console.log('\n--- Perimeter Calculation ---')
drawing.accept(perimeterCalculator)
--- Area Calculation ---
Circle area: 78.54
Rectangle area: 24

--- Perimeter Calculation ---
Circle perimeter: 31.42
Rectangle perimeter: 20

优点

  • 开闭原则:新增操作只需添加访问者,无需修改元素类。
  • 单一职责:将相关操作集中到访问者中,分离数据结构与算法。
  • 扩展性好:可轻松添加新操作(如新增 VolumeCalculator)。
  • 状态累积:访问者可在遍历过程中收集数据(如计算总面积)。

缺点

  • 破坏封装:元素需暴露内部状态供访问者操作(如 circle.radius)。
  • 增加新元素困难:新增元素类型需修改所有访问者(违反开闭原则)。
  • 不适合元素类频繁变化的场景。
  • 可能引入循环依赖(元素与访问者相互依赖)。

适用场景

  • 对象结构稳定,但需频繁添加新操作。
  • 需要对复杂结构(如 AST、DOM 树)执行多种独立操作。
  • 避免污染元素类代码(如分离业务逻辑与数据模型)。
  • 跨多个类执行统一操作(如报表生成、导出功能)。

典型应用案例

  • 抽象语法树(AST)处理:编译器中的类型检查、代码优化。
  • 文档处理:导出 HTML/PDF、拼写检查。
  • UI 组件树:渲染、布局计算。
  • 游戏开发:角色属性计算、碰撞检测。