操作复杂对象结构——访问者模式(二)

26.2 访问者模式概述

访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。例如处方单中的各种药品信息就是被访问的元素,而划价人员和药房工作人员就是访问者。访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。

在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。

访问者模式定义如下:

访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

访问者模式的结构较为复杂,其结构如图26-2所示:

操作复杂对象结构——访问者模式(二) - 图1

在访问者模式结构图中包含如下几个角色:

●Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。

●ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。

●Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。【稍后将介绍为什么要这样设计。】

●ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。

● ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。

访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

在访问者模式中,抽象访问者定义了访问元素对象的方法,通常为每一种类型的元素对象都提供一个访问方法,而具体访问者可以实现这些访问方法。这些访问方法的命名一般有两种方式:一种是直接在方法名中标明待访问元素对象的具体类型,如visitElementA(ElementA elementA),还有一种是统一取名为visit(),通过参数类型的不同来定义一系列重载的visit()方法。当然,如果所有的访问者对某一类型的元素的访问操作都相同,则可以将操作代码移到抽象访问者类中,其典型代码如下所示:

  1. abstract class Visitor
  2. {
  3. public abstract void visit(ConcreteElementA elementA);
  4. public abstract void visit(ConcreteElementB elementB);
  5. public void visit(ConcreteElementC elementC)
  6. {
  7. //元素ConcreteElementC操作代码
  8. }
  9. }

在这里使用了重载visit()方法的方式来定义多个方法用于操作不同类型的元素对象。在抽象访问者Visitor类的子类ConcreteVisitor中实现了抽象的访问方法,用于定义对不同类型元素对象的操作,具体访问者类典型代码如下所示:

  1. class ConcreteVisitor extends Visitor
  2. {
  3. public void visit(ConcreteElementA elementA)
  4. {
  5. //元素ConcreteElementA操作代码
  6. }
  7. public void visit(ConcreteElementB elementB)
  8. {
  9. //元素ConcreteElementB操作代码
  10. }
  11. }

对于元素类而言,在其中一般都定义了一个accept()方法,用于接受访问者的访问,典型的抽象元素类代码如下所示:

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

需要注意的是该方法传入了一个抽象访问者Visitor类型的参数,即针对抽象访问者进行编程,而不是具体访问者,在程序运行时再确定具体访问者的类型,并调用具体访问者对象的visit()方法实现对元素对象的操作。在抽象元素类Element的子类中实现了accept()方法,用于接受访问者的访问,在具体元素类中还可以定义不同类型的元素所特有的业务方法,其典型代码如下所示:

  1. class ConcreteElementA implements Element
  2. {
  3. public void accept(Visitor visitor)
  4. {
  5. visitor.visit(this);
  6. }
  7. public void operationA()
  8. {
  9. //业务方法
  10. }
  11. }

在具体元素类ConcreteElementA的accept()方法中,通过调用Visitor类的visit()方法实现对元素的访问,并以当前对象作为visit()方法的参数。其具体执行过程如下:

(1) 调用具体元素类的accept(Visitor visitor)方法,并将Visitor子类对象作为其参数;

(2) 在具体元素类accept(Visitor visitor)方法内部调用传入的Visitor对象的visit()方法,如visit(ConcreteElementA elementA),将当前具体元素类对象(this)作为参数,如visitor.visit(this);

(3) 执行Visitor对象的visit()方法,在其中还可以调用具体元素对象的业务方法。

这种调用机制也称为“双重分派”,正因为使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的accept()方法,程序运行时将回调在新增Visitor类中定义的visit()方法,从而增加新的元素访问方式。

思考

双重分派机制如何用代码实现?

在访问者模式中,对象结构是一个集合,它用于存储元素对象并接受访问者的访问,其典型代码如下所示:

  1. class ObjectStructure
  2. {
  3. private ArrayList<Element> list = new ArrayList<Element>(); //定义一个集合用于存储元素对象
  4. public void accept(Visitor visitor)
  5. {
  6. Iterator i=list.iterator();
  7. while(i.hasNext())
  8. {
  9. ((Element)i.next()).accept(visitor); //遍历访问集合中的每一个元素
  10. }
  11. }
  12. public void addElement(Element element)
  13. {
  14. list.add(element);
  15. }
  16. public void removeElement(Element element)
  17. {
  18. list.remove(element);
  19. }
  20. }

在对象结构中可以使用迭代器对存储在集合中的元素对象进行遍历,并逐个调用每一个对象的accept()方法,实现对元素对象的访问操作。

思考

访问者模式是否符合“开闭原则”?【从增加新的访问者和增加新的元素两方面考虑。】