访问者模式

访问者模式是一种集中规整模式,特别适用于大规模重构的项目,通过访问者模式可以很容易把一些功能进行梳理。还可以与其他模式混编建立一套自己的过滤器或者拦截器

访问者模式是对迭代器模式的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同, 执行不同的操作。 访问者模式还可以充当拦截器Interceptor角色 。

访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性

定义

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的
操作

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作数据结构进行分离,是行为类模式最复杂的一种模式。

实现

抽象访问者Visitor:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit() ,该操作中的参数类型标识了被访问的具体元素。

1
2
3
4
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}

具体访问者角色ConcreteVisitor:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时的具体功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}

@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->" + element.operationB());
}
}

public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者B访问-->" + element.operationA());
}

@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者B访问-->" + element.operationB());
}
}

抽象元素角色Element:声明一个包含接受操作accept()的接口,被接受的访问者对象作为accept()方法的参数。

1
2
3
public interface Element {
void accept(Visitor visitor);
}

具体元素角色ConcreteElement:实现抽象元素角色提供的 accept() 操作,其方法体通常都是visitor.visit(this),另外具体元素中可能还包含本身业务逻辑的相关操作

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

public String operationA() {
return "具体元素A的操作。";
}
}

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

public String operationB() {
return "具体元素B的操作。";
}
}

对象结构角色ObjectStructure:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

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
public class ObjectStructure {
private List<Element> list = new ArrayList<Element>();

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

public void add(Element element) {
list.add(element);
}

public void remove(Element element) {
list.remove(element);
}

public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());
Visitor visitor = new ConcreteVisitorA();
os.accept(visitor);
System.out.println("------------------------");
visitor = new ConcreteVisitorB();
os.accept(visitor);
}
}

优点

  • 扩展性好:能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好:可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  • 符合单一职责原则:访问者模式把相关行为封装在一起构成一个访问者,使每个访问者功能都比较单一。

缺点

  • 增加新的元素类很困难:每增加一个新的元素类,都要在每个具体访问者类中增加相应的具体操作,违背开闭原则
  • 破坏封装具体元素对访问者公布细节,这破坏了对象的封装性
  • 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。

应用

  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作污染这些对象的类
  • 数据元素相对稳定而访问方式多种多样的数据结构
  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  • 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

扩展

  • 统计功能:数据统计和报表的批处理通过访问者模式来处理会比较简单
  • 多个访问者:用于展示的访问者和用于汇总的访问者