25.2 访问者模式的定义

25.2 访问者模式的定义

访问者模式(Visitor Pattern)是一个相对简单的模式,其定义如下:

Represent an operation to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it operates.(封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)

访问者模式的通用类图如图25-5所示。

image-20210930102720082

图25-5 访问者模式的通用类图

看了这个通用类图,大家可能要犯迷糊了,这里怎么有一个ObjectStruture类呢?你刚刚举的例子怎么就没有呢?真没有吗?我们不是定义了一个List了吗?它中间的元素是我们一个一个手动增加上去的,这就是一个ObjectStruture,我们来看这几个角色的职责。

  • Visitor——抽象访问者

抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。

  • ConcreteVisitor——具体访问者

它影响访问者访问到一个类后该怎么干,要做什么事情。

  • Element——抽象元素

接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。

  • ConcreteElement——具体元素

实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了。

  • ObjectStruture——结构对象

元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。

大家可以这样理解访问者模式,我作为一个访客(Visitor)到朋友家(Visited Class)去拜访,朋友之间聊聊天,喝喝酒,再相互吹捧吹捧,炫耀炫耀,这都正常。聊天的时候,朋友告诉我,他今年加官晋爵了,工资也涨了30%,准备再买套房子,那我就在心里盘算 (Visitor-self-method)“你这么有钱,我去年要借10万你都不借”,我根据朋友的信息,执行了自己的一个方法。

我们来看看访问者模式的通用源码,先看抽象元素,如代码清单25-11所示。

代码清单25-11 抽象元素

1
2
3
4
5
6
public abstract class Element {
//定义业务逻辑
public abstract void doSomething();
//允许谁来访问
public abstract void accept(IVisitor visitor);
}

抽象元素有两类方法:一是本身的业务逻辑,也就是元素作为一个业务处理单元必须完成的职责;另外一个是允许哪一个访问者来访问。我们来看具体元素,如代码清单25-12所示。

代码清单25-12 具体元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ConcreteElement1 extends Element{
//完善业务逻辑
public void doSomething(){
//业务处理
}
//允许那个访问者访问
public void accept(IVisitor visitor){
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element{
//完善业务逻辑
public void doSomething(){
//业务处理
}
//允许那个访问者访问
public void accept(IVisitor visitor){
visitor.visit(this);
}
}

它定义了两个具体元素,我们再来看抽象访问者,一般是有几个具体元素就有几个访问方法,如代码清单25-13所示。

代码清单25-13 抽象访问者

1
2
3
4
5
public interface IVisitor {
//可以访问哪些对象
public void visit(ConcreteElement1 el1);
public void visit(ConcreteElement2 el2);
}

具体访问者如代码清单25-14所示。

代码清单25-14 具体访问者

1
2
3
4
5
6
7
8
9
10
public class Visitor implements IVisitor {
//访问el1元素
public void visit(ConcreteElement1 el1) {
el1.doSomething();
}
//访问el2元素
public void visit(ConcreteElement2 el2) {
el2.doSomething();
}
}

结构对象是产生出不同的元素对象,我们使用工厂方法模式来模拟,如代码清单25-15 所示。

代码清单25-15 结构对象

1
2
3
4
5
6
7
8
9
10
11
12
public class ObjectStruture {
//对象生成器,这里通过一个工厂方法模式模拟
public static Element createElement(){
Random rand = new Random();
if(rand.nextInt(100) > 50){
return new ConcreteElement1();
}
else{
return new ConcreteElement2();
}
}
}

进入了访问者角色后,我们对所有的具体元素的访问就非常简单了,我们通过一个场景类模拟这种情况,如代码清单25-16所示。

代码清单25-16 场景类

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
for(int i=0;i<10;i++){
//获得元素对象
Element el = ObjectStruture.createElement();
//接受访问者访问
el.accept(new Visitor());
}
}
}

通过增加访问者,只要是具体元素就非常容易访问,对元素的遍历就更加容易了,甭管它是什么对象,只要它在一个容器中,都可以通过访问者来访问,任务集中化。这就是访问者模式。