软件设计模式修炼 -- 访问者模式
访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素具有不同的类型,且不同的访问者可以对其进行不同的访问操作
模式动机
对于系统中某些对象,它们存储在同一个集合中,且具有不同的类型。对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访问者其访问方式有所不同。
在 Java 等面向对象语言中都提供了大量用于存储多个元素的集合对象,集合中存储的对象有时候是同一类型,有时候不是同一类型,或许它们只是具有共同的父类。假如我们要针对一个包含不同类型元素的集合采取某种操作,而操作细节根据元素类型不同而不同,就会出现大量类型判断语句,增大代码复杂度。
实际使用时,对相同元素的对象也可能存在多种不同的操作方式,而且可能还需要增加新的操作,此时访问者模式是一个值得考虑的解决方案。
模式定义
表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
Represent an operation to be performedd 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.
模式分析
访问者模式结构较为复杂,首先看一张模式结构类图
对象结构(ObjectStructure)是一个元素集合,存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次,一个是访问者层次结构,另一个是元素层次结构。
访问者层次结构提供了抽象访问者(Visitor)和具体访问者(ConcreteVisitor)。抽象访问者声明了访问元素对象的方法,通常为每一种类型的元素对象都提供一个访问方法,而具体访问者可以实现这些访问方法。
这些访问方法的设计又有两种,一种是直接在方法名中标明待访问元素对象的类型,如 visitConcreteElementA(ConcreteElementA elementA),还有一种是统一取名为 visit(),通过参数类型的不同来定义一系列重载方法。
public abstract class Visitor {
// 统一取名
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
// 如果所有访问者对某一类型的元素访问操作都相同
// 则可以将操作代码移到抽象访问者中
public void visit(ConcreteElementC elementC) {
...
}
}
元素层次结构提供了抽象元素类(Element)和具体元素类(ConcreteElementA),抽象元素类一般都声明一个 accept() 方法,用于接受访问者的访问。该方法传入一个抽象访问者 Visitor 类型的参数,在程序运行时确定其具体访问者的类型,并调用具体访问者对象的 visit() 方法实现对元素对象的操作
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
// 在具体元素类中可以定义不同类型的元素所特有的业务方法
}
}
具体元素类 ConcreteElementA 的 accept() 方法通过调用 Visitor 类的 visit() 方法实现对元素的访问,并以当前对象作为 visit() 方法的参数,这种调用机制也称“双重分派”。正因为使用了双重分派技术,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象传入具体元素对象的 accept() 方法即可,程序运行时将回调在 Visitor 类中定义的 visit() 方法,从而实现不同形式的访问。
对象结构(ObjectStructure)是一个集合,用于存储元素对象并接受访问者的访问。在对象结构中可以使用迭代器对存储在集合中的元素对象进行遍历,并逐个调用每一个对象的 accept() 方法,实现对元素对象的访问操作。
public class ObjectStructure {
private ArrayList list = new ArrayList();
public void accept(Visitor visitor) {
Iterator i = list.iterator();
while(i.hashNext()) {
((Element)i.next()).accept(visitor);
}
}
public void addElement(Element element) {
list.add(element);
}
public void removeElement(Element element) {
list.remove(element);
}
}
最终在客户端我们需要实例化一个对象结构对象,并向其添加元素对象,再调用 accept() 方法来接受访问者对象的访问。具体访问者类型可以通过配置文件来确定。
public class Client {
public static void main(String[] args) {
Element elementA = new ElementA();
Element elementB = new ElementB();
Element elementC = new ElementC();
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addElement(elementA);
objectStructure.addElement(elementB);
objectStructure.addElement(elementC);
Visitor visitor = new ConcreteVisitorA();
objectStructure.accept(visitor);
}
}
如果需要修改访问者类型,只或者增加新的类型的访问者,只需修改配置文件即可,符合开闭原则。但如果要增加新的类型的具体元素类,则访问者类需要为其定义新的访问方法,从这一点看又违背了开闭原则。
模式优缺点
访问者模式的优点:
- 使得增加新的访问操作变得容易,无须修改现有类库的代码
- 将有关类对象的访问行为i集中到一个访问者对象中,而不是分散到一个个元素类,类的职责更加清晰
- 可以跨过类的等级结构访问不同等级结构的元素类
- 用户能够在不修改现有类层次结构的情况下,定义该类层次结构的新操作
访问者模式的缺点:
- 增加新的元素类很困难
- 访问者模式要求访问者对象访问并调用每一个元素对象的操作,破坏了封装性