设计模式:访问者模式 探究

访问者模式简介

访问者模式是一种行为模式。

访问者模式是一种将数据操作和数据结构分离的设计模式。(觉得太抽象,可以看下面的例子)。

使用场景

  1. 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

优缺点

优点

  1. 各角色职责分离,符合单一职责原则
    通过后面UML类图和示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。
  2. 具有优秀的扩展性
    如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
  3. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
    城市经典(数据结构)和跟团游游客、自驾游游客访问者(数据操作)的解耦。
  4. 灵活性

缺点

  1. 具体元素对访问者公布细节,违反了迪米特原则
    团游游客、自驾游游客需要调用具体城市的方法。
  2. 具体元素变更时导致修改成本大
    变更城市属性时,多个访问者都要修改,比如雷峰塔倒了。
  3. 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象
    访问者 visit 方法中,依赖了具体城市的具体方法。

通用模板

UML 图

角色介绍

  • Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
  • ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
    Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
  • ElementA、ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
  • client: 具体的运行代码类

通用代码

Element接口

1
2
3
4
5
6
7
8
/**
* 元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
* */
public interface Element {

public void accept(Visitor visitor);

}

Element Concrete 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public void operation1(){
System.out.println("ElementA do operation1");
}
}

public class ElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public void operation2(){
System.out.println("ElementB do operation2");
}
}

visitor接口

1
2
3
4
5
6
7
8
9
10
/**
* 定义了对每个 Element 访问的行为
* */
public interface Visitor {

public void visit(ElementA element1);

public void visit(ElementB element2);

}

visitor concrete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class VisitorA implements Visitor {
@Override
public void visit(ElementA elementA) {
System.out.println("Visitor A start visit ElementA");
elementA.operation1();
}

@Override
public void visit(ElementB elementB) {
System.out.println("Visitor A start visit ElementB");
elementB.operation2();
}
}

public class VisitorB implements Visitor {
@Override
public void visit(ElementA elementA) {
System.out.println("Visitor B start visit ElementA");
elementA.operation1();
}

@Override
public void visit(ElementB elementB) {
System.out.println("Visitor B start visit ElementB");
elementB.operation2();
}
}

ObjectStructure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ObjectStructure {

private List<Element> elementList;

public ObjectStructure(){
elementList=new ArrayList<Element>();
elementList.add(new ElementA());
elementList.add(new ElementB());
}

public void accept(Visitor visitor){
for(Element element:elementList){
element.accept(visitor);
}
}

public void addElement(Element element){
elementList.add(element);
}

public void removeElement(Element element){
elementList.remove(element);
}
}

最后client调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {

private Visitor visitor;

private ObjectStructure objectStructure;

public void doVisit(){
ObjectStructure objectStructure=new ObjectStructure();
objectStructure.accept(new VisitorA());
objectStructure.accept(new VisitorB());
}

public static void main(String[] args){
Client client=new Client();
client.doVisit();
}
}

执行结果

1
2
3
4
5
6
7
8
Visitor A start visit ElementA
ElementA do operation1
Visitor A start visit ElementB
ElementB do operation2
Visitor B start visit ElementA
ElementA do operation1
Visitor B start visit ElementB
ElementB do operation2

依然很抽象,看下面的例子吧。

旅游城市例子

访问者模式,用游客访问城市经典的例子再合适不过了.

不同的游客有不同的访问行为,跟团游和自驾游或休闲游在游览景点是的行为也是完全不相同的,完美符合访问者模式中的访问者这一对象。

UML 图

角色介绍

  • City
    城市充当Element角色,各城市属性非常稳定,但是城市所属的景点可能发生轻微改造或变更,完美符合Element角色的要求。
  • HangzhouCity,HuangshanCity,BeijingCity
    三个城市的具体实现:杭州、黄山、北京,对应的是ElementConcrete
  • Tourists
    游客接口,游客可以访问城市,对应访问者模式中的Visitor角色。
  • WithTheGroupTourists,SelfDriverTourists
    游客的两个具体实现,跟团游游客和自驾游游客,对应VisitorConcrete
  • TravelPlan
    旅游计划抽象,封装了一堆城市的集合。
    对应ObjectStructure
  • TravelClient
    具体的一次旅游,对应 Client

实例代码

City

1
2
3
4
5
6
7
8
/**
* 城市,对应访问者模式中的Element元素,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
* */
public interface City {

public void accept(Tourists visitor);

}

CityConcrete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* 北京,city的具体实现
* * 对应visitor中的ElementConcrete
* */
public class BeijingCity implements City {
@Override
public void accept(Tourists tourists) {
tourists.visitBeijing(this);
}

/**
* 旅游长城
* */
public String greatWall(){
return "长城";
}

/**
* 旅游故宫
* */
public String forbiddenCity(){
return "故宫";
}

/**
* 后海酒吧
* */
public String houhaiBar(){
return "后海酒吧";
}
}

/**
* 杭州,city的具体实现
* 对应visitor中的ElementConcrete
* */
public class HangzhouCity implements City {
@Override
public void accept(Tourists tourists) {
tourists.visitHangzhou(this);
}

/**
* 西湖
* */
public String westLake(){
return "西湖";
}

/**
* 浙江大学
* */
public String zhejiangUniversity(){
return "浙江大学";
}

/**
* 雷峰塔
* */
public String leifengPagoda(){
return "雷峰塔";
}
}

/**
* 黄山,city的具体实现
* 对应visitor中的ElementConcrete
* */
public class HuangshanCity implements City {
@Override
public void accept(Tourists tourists) {
tourists.visitHuangshan(this);
}

/**
* 黄山
* */
public String huangshan(){
return "黄山";
}
}

Tourists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

/**
* 游客,对应访问者模式中的Visitor
* 定义了对每个 Element 访问的行为
* */
public interface Tourists {

/**
* 访问北京
* */
public void visitBeijing(BeijingCity beijingCity);

/**
* 访问杭州
* */
public void visitHangzhou(HangzhouCity hangzhouCity);

/**
* 访问黄山
* */
public void visitHuangshan(HuangshanCity huangshanCity);

}

TouristsConcrete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 自驾游,Tourists实现,对应访问者模式中VisitorConcrete
* */
public class SelfDriveTourists implements Tourists {

@Override
public void visitBeijing(BeijingCity beijingCity) {
System.out.println(">>>>>开始自驾旅游北京");
System.out.println("第1站:"+beijingCity.greatWall());
System.out.println("第2站:"+beijingCity.forbiddenCity());
System.out.println("第3站:"+beijingCity.houhaiBar());
System.out.println(">>>>>结束自驾旅游北京");

}

@Override
public void visitHangzhou(HangzhouCity hangzhouCity) {
System.out.println(">>>>>开始自驾旅游杭州");
System.out.println("第1站:"+hangzhouCity.westLake());
System.out.println("第2站:"+hangzhouCity.leifengPagoda());
System.out.println("第3站:"+hangzhouCity.zhejiangUniversity());
System.out.println(">>>>>结束自驾旅游杭州");
}

@Override
public void visitHuangshan(HuangshanCity huangshanCity) {
System.out.println(">>>>>开始自驾旅游黄山");
System.out.println("第1站:"+huangshanCity.huangshan());
System.out.println(">>>>>结束自驾旅游黄山");
}
}

/**
* 跟团游,Tourists的实现
* 对应访问者模式中VisitorConcrete
* */
public class WithTheGroupTourists implements Tourists {
@Override
public void visitBeijing(BeijingCity beijingCity) {
System.out.println(">>>>>开始跟团旅游北京");
System.out.println("第1站:"+beijingCity.houhaiBar());
System.out.println("第2站:"+beijingCity.forbiddenCity());
System.out.println("跟团游时间太紧,无法旅游:"+beijingCity.greatWall());
System.out.println(">>>>>结束跟团旅游北京");

}

@Override
public void visitHangzhou(HangzhouCity hangzhouCity) {
System.out.println(">>>>>开始跟团旅游杭州");
System.out.println("第1站:"+hangzhouCity.westLake());
System.out.println("第2站:"+hangzhouCity.zhejiangUniversity());
System.out.println("跟团游时间太紧,无法旅游:"+hangzhouCity.leifengPagoda());
System.out.println(">>>>>结束跟团旅游杭州");
}

@Override
public void visitHuangshan(HuangshanCity huangshanCity) {
System.out.println(">>>>>开始跟团旅游黄山");
System.out.println("第1站:"+huangshanCity.huangshan());
System.out.println(">>>>>结束跟团旅游黄山");
}
}

TravelPlan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 旅游计划
* 对应访问者模式中的ObjectStructure
* */
public class TravelPlan {

private List<City> cityList;

public TravelPlan(){
cityList=new ArrayList<City>();
cityList.add(new BeijingCity());
cityList.add(new HangzhouCity());
cityList.add(new HuangshanCity());

}

public void travel(Tourists visitor){
for(City city:cityList){
city.accept(visitor);
}
}

public void addCity(City city){
cityList.add(city);
}

public void removeCity(City city){
cityList.remove(city);
}
}

TravelClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 旅游记录
* 对应访问者模式中的Client
* */
public class TravelClient {

private Tourists visitor;

private TravelPlan travelPlan;

public void doTravel(){
TravelPlan travelPlan=new TravelPlan();
//跟团游
travelPlan.travel(new WithTheGroupTourists());
System.out.println();
System.out.println();

//自驾游
travelPlan.travel(new SelfDriveTourists());
}

public static void main(String[] args){
TravelClient travelClient=new TravelClient();
travelClient.doTravel();
}
}

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>>>>开始跟团旅游北京
第1站:后海酒吧
第2站:故宫
跟团游时间太紧,无法旅游:长城
>>>>>结束跟团旅游北京
>>>>>开始跟团旅游杭州
第1站:西湖
第2站:浙江大学
跟团游时间太紧,无法旅游:雷峰塔
>>>>>结束跟团旅游杭州
>>>>>开始跟团旅游黄山
第1站:黄山
>>>>>结束跟团旅游黄山


>>>>>开始自驾旅游北京
第1站:长城
第2站:故宫
第3站:后海酒吧
>>>>>结束自驾旅游北京
>>>>>开始自驾旅游杭州
第1站:西湖
第2站:雷峰塔
第3站:浙江大学
>>>>>结束自驾旅游杭州
>>>>>开始自驾旅游黄山
第1站:黄山
>>>>>结束自驾旅游黄山

代码

以上例子代码可在个人github项目design-patterns中找到。

参考文章

访问者模式一篇就够了