访问者模式
访问者模式(Visitor Pattern)是一种行为型设计模式 ,核心思想是将算法与对象结构分离,使得在不修改现有对象结构的前提下,可以动态添加新的操作。这类似于"医生查房"------医生(访问者)根据病人(元素)的不同病情执行不同的诊疗操作,而病人本身不需要修改自己的病历结构。
一、通俗理解
假设你开发一个图形处理软件:
- 传统方式:在图形类(如圆形、矩形)中直接添加各种操作(如计算面积、导出SVG),导致类臃肿且难以扩展新功能。
- 访问者模式 :
- 图形类 (元素)仅保留基础属性,提供接受访问者的接口(
accept()
)。 - 访问者 (如面积计算器、SVG导出器)实现具体操作逻辑,通过访问不同图形执行对应行为。
这样新增功能时,只需添加新的访问者类,无需修改图形类代码。
- 图形类 (元素)仅保留基础属性,提供接受访问者的接口(
二、模式结构
- Visitor(访问者接口) :定义对不同元素的访问方法(如
visitCircle()
、visitRectangle()
)。 - ConcreteVisitor(具体访问者):实现具体操作(如面积计算)。
- Element(元素接口) :定义接受访问者的方法(
accept()
)。 - ConcreteElement(具体元素) :实现
accept()
,调用访问者的对应方法。 - ObjectStructure(对象结构):管理元素集合,提供遍历访问的入口。
三、适用场景
- 复杂对象结构的动态操作扩展:如编译器语法树分析、游戏角色行为管理。
- 数据与操作解耦:需对同一数据执行多种独立操作(如统计、日志、渲染)。
- 避免污染元素类:当操作逻辑频繁变化时,保持元素类稳定。
四、代码实现
1. C++ 示例(图形处理)
cpp
#include <iostream>
#include <vector>
// 前向声明(用于访问者接口)
class Circle;
class Rectangle;
// 访问者接口
class Visitor {
public:
virtual void visit(Circle* circle) = 0;
virtual void visit(Rectangle* rect) = 0;
};
// 元素接口
class Shape {
public:
virtual void accept(Visitor* visitor) = 0;
};
// 具体元素:圆形
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
void accept(Visitor* visitor) override { visitor->visit(this); }
double getRadius() const { return radius; }
};
// 具体访问者:面积计算器
class AreaCalculator : public Visitor {
public:
void visit(Circle* circle) override {
double area = 3.14 * circle->getRadius() * circle->getRadius();
std::cout << "圆形面积:" << area << std::endl;
}
void visit(Rectangle* rect) override {
// 类似实现矩形面积计算(略)
}
};
// 对象结构
class Drawing {
std::vector<Shape*> shapes;
public:
void addShape(Shape* shape) { shapes.push_back(shape); }
void applyVisitor(Visitor* visitor) {
for (auto shape : shapes) shape->accept(visitor);
}
};
int main() {
Drawing drawing;
drawing.addShape(new Circle(5.0));
AreaCalculator areaVisitor;
drawing.applyVisitor(&areaVisitor); // 输出:圆形面积:78.5
}
解析:
Circle
类仅维护半径属性,通过accept()
将自身传递给访问者。- 新增功能(如导出SVG)只需添加新的
Visitor
实现类。
2. Python 示例(员工薪资调整)
python
from abc import ABC, abstractmethod
class Employee(ABC):
@abstractmethod
def accept(self, visitor):
pass
class Manager(Employee):
def __init__(self, name, salary):
self.name = name
self.salary = salary
def accept(self, visitor):
visitor.visit_manager(self)
class SalaryAdjustVisitor:
def visit_manager(self, manager):
manager.salary *= 1.1
print(f"{manager.name} 薪资调整为 {manager.salary}")
# 客户端
manager = Manager("Alice", 10000)
visitor = SalaryAdjustVisitor()
manager.accept(visitor) # 输出:Alice 薪资调整为 11000.0
特点:
- 动态语言无需严格接口继承,但抽象基类可提高代码规范性。
3. Java 示例(商品折扣计算)
java
interface Visitor {
void visit(Book book);
void visit(Electronic electronic);
}
class DiscountVisitor implements Visitor {
public void visit(Book book) {
System.out.println("书籍《" + book.getTitle() + "》打8折");
}
public void visit(Electronic electronic) {
System.out.println("电子产品" + electronic.getName() + "打9折");
}
}
class ShoppingCart {
private List<Product> items = new ArrayList<>();
public void applyDiscount(Visitor visitor) {
for (Product item : items) item.accept(visitor);
}
}
// 测试
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Book("设计模式"));
cart.applyDiscount(new DiscountVisitor());
// 输出:书籍《设计模式》打8折
应用场景:电商平台对不同商品类型执行差异化促销策略。
五、优缺点分析
优点 | 缺点 |
---|---|
1. 开闭原则:新增操作不修改元素类 | 1. 类数量膨胀:每新增操作需新访问者类 |
2. 职责分离:数据与算法解耦 | 2. 破坏封装性:访问者需访问元素内部属性 |
3. 集中管理操作:同类逻辑聚合 | 3. 元素类型受限:新增元素需修改所有访问者 |
六、总结
访问者模式通过双重分派机制(元素调用访问者的方法,访问者再回调元素方法),实现了算法与对象结构的动态解耦。其核心价值在于:
- 扩展性:适用于操作频繁变化但对象结构稳定的系统(如财务审计、游戏引擎)。
- 实际应用:Spring框架的Bean处理器、编译器语法树分析均采用此模式。
扩展对比:
- 与策略模式:策略模式关注算法替换,访问者模式关注跨对象结构的操作扩展。
- 与组合模式:常结合使用,遍历树形结构时统一处理节点。