深入浅出访问者模式
访问者模式,是行为型设计模式之一。访问者模式是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。
访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。
定义
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
可以对定义这么理解:有这么一个操作,它是作用于一些元素之上的,而这些元素属于某一个对象结构。同时这个操作是在不改变各元素类的前提下,在这个前提下定义新操作是访问者模式精髓中的精髓。关键代码是在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:
-
扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
-
复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
-
灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
-
符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
主要缺点如下:
-
增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
-
破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
-
违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
基本结构
访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其 UML 类图如下:
角色介绍
-
Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
-
ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
-
Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
-
ElementA、ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
-
ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
模式的实现
访问者模式的实现代码如下:
package net.biancheng.c.visitor; import java.util.*; public class VisitorPattern { 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); } } //抽象访问者 interface Visitor { void visit(ConcreteElementA element); void visit(ConcreteElementB element); } //具体访问者A类 class ConcreteVisitorA implements Visitor { public void visit(ConcreteElementA element) { System.out.println("具体访问者A访问-->" + element.operationA()); } public void visit(ConcreteElementB element) { System.out.println("具体访问者A访问-->" + element.operationB()); } } //具体访问者B类 class ConcreteVisitorB implements Visitor { public void visit(ConcreteElementA element) { System.out.println("具体访问者B访问-->" + element.operationA()); } public void visit(ConcreteElementB element) { System.out.println("具体访问者B访问-->" + element.operationB()); } } //抽象元素类 interface Element { void accept(Visitor visitor); } //具体元素A类 class ConcreteElementA implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public String operationA() { return "具体元素A的操作。"; } } //具体元素B类 class ConcreteElementB implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public String operationB() { return "具体元素B的操作。"; } } //对象结构角色 class ObjectStructure { private List<Element> list = new ArrayList<Element>(); public void accept(Visitor visitor) { Iterator<Element> i = list.iterator(); while (i.hasNext()) { ((Element) i.next()).accept(visitor); } } public void add(Element element) { list.add(element); } public void remove(Element element) { list.remove(element); } }
程序的运行结果如下:
具体访问者A访问-->具体元素A的操作。 具体访问者A访问-->具体元素B的操作。 ------------------------ 具体访问者B访问-->具体元素A的操作。 具体访问者B访问-->具体元素B的操作。
模式的应用场景
当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。
简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。
通常在以下情况可以考虑使用访问者(Visitor)模式。
-
对象结构相对稳定,但其操作算法经常变化的程序。
-
对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
-
对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
模式的扩展
访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。
-
与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如【例1】中的对象结构是用 List 实现的,它通过 List 对象的 Iterator() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。
-
访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式,其结构图如下图 所示。
总结
其实很早之前也看过访问者模式,但是当时一直不能理解这个设计模式的实现,感觉就是很难理解。今天在看,其实也没有那么难,之所以难还是概念理解不够深入。
访问者模式其实关键的就是两个类:Visitor 和 Element。 Element 有个关键的方法 accept 方法,表示接受 Visitor 的访问;Visitor 有个关键的的方法是 visit() 方法,参数是 Element,这样就可以获取到任何关于 Element 的信息。然后就可以按照需求随意组合 Element 提供的信息。
只要把上面两个理解清楚了,其他几个类无非就是接口类,还有个对象结构,整个设计模式的目的就不难理解了。
最后除了理解设计模式的目的,还要理解什么情况下去用,这样的话,你对一个设计模式思想就基本就掌握了。
参考文章