第十六章:访问者模式
一、基本介绍
现在我们有这么几个类:足球类、篮球类、排球类。现在需要实现获取各个球重量的功能。那么直接在各个球类中加一个获取重量的方法就行了。那么问题来了,现在又需要获取各个球的颜色。那我们还是修改各个类增加个新方法?那如果以后有更多的新需求还是这么做?这显然违反了 OCP 原则。
所以我们使用访问者模式解决这个问题。
访问者模式基本类图:
- Visitor 里面的 operation 的数量往往对应着具体 Element 的种类数
- 每新增一个需求只需新建一个 ConcreteVisitor 类
- Element 具体的子类数应该是不轻易改变的,因为这会影响到所有访问者类
二、案例解决
抽象元素类 Ball
@Data
public abstract class Ball {
private int id;
private int diameter;
private int weight;
private String color;
abstract public void accept(Visitor visitor);
}
具体元素子类
Basketball
public class Basketball extends Ball {
@Override
public void accept(Visitor visitor) {
visitor.operateBasketball(this);
}
}
Football
public class Football extends Ball{
@Override
public void accept(Visitor visitor) {
visitor.operateFootball(this);
}
}
Volleyball
public class Volleyball extends Ball{
@Override
public void accept(Visitor visitor) {
visitor.operateVolleyball(this);
}
}
抽象访问者类 Visitor
public abstract class Visitor {
// 根据操作的元素类第二次分派
abstract public void operateBasketball(Basketball ball);
abstract public void operateFootball(Football ball);
abstract public void operateVolleyball(Volleyball ball);
}
具体子类访问者
WeightVisitor
public class WeightVisitor extends Visitor {
@Override
public void operateBasketball(Basketball ball) {
// 为什么不直接调用 ball.getWeight(),搞这么麻烦
// 实际情况下可能有很多输出格式,比如 xml 形式什么的
// 所以直接调用 ball.getWeight() 往往是不满足需求的
System.out.println("篮球重量:" + ball.getWeight());
}
@Override
public void operateFootball(Football ball) {
System.out.println("足球重量:" + ball.getWeight());
}
@Override
public void operateVolleyball(Volleyball ball) {
System.out.println("排球重量:" + ball.getWeight());
}
}
测试类
public class VisitorTest {
public static void main(String[] args) {
Football football = new Football();
football.setWeight(200);
WeightVisitor weightVisitor = new WeightVisitor();
// 第一次分派
football.accept(weightVisitor);
}
}
现在如果我们新增一个打印颜色的需求是不是只需添加一个打印颜色功能的访问者就行了呢!
上面的案例使用到了双分派,element.accept(visitor) 根据访问者第一次分派,visitor 里根据操作的元素类第二次分派。这样就能实现特定元素特定功能
三、模式总结
1)适合有稳定的数据结构,如上述案例 Ball 的子类就那么多不怎么新增
2)有经常变化的功能需求,只需新增一个 Visitor 实现类就行了
3)可以做报表、UI、拦截器、过滤器
4)但是我们的元素类对访问者类可以说开诚布公,违反迪米特法则