`
chencvgk
  • 浏览: 15562 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

访问者模式 ( Visitor )

 
阅读更多
引子
对于系统中一个已经完成的类层次结构,我们已经给它提供了满足需求的接口。但是面对新增加的需求,我们应该怎么做呢?如果这是为数不多的几次变动,而且你不用为了一个需求的调整而将整个类层次结构统统地修改一遍,那么直接在原有类层次结构上修改也许是个不错的主意。
但是往往我们遇到的却是:这样的需求变动也许会不停的发生;更重要的是需求的任何变动可能都要让你将整个类层次结构修改个底朝天。这种类似的操作分布在不同的类里面,不是一个好现象,我们要对这个结构重构一下了。
那么,访问者模式也许是你很好的选择。

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

访问者模式定义
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于java中的collection概念了。


如果不使用模式
类似的操作分布在不同的类里面,不是一个好现象。会导致整个系统难以理解难以维护。
每扩展一个新的功能,都需要改动相关的类,当然也可以通过为它们扩展子类的方式,但这样可能会造成过多的对象层次。
使用了这个模式以后就可以在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。

访问者模式的结构


  • Visitor: 访问者接口,为所有的访问者对象声明一个visitor方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能。
  • ConcreteVisitor:具体的访问者实现对象,实现要真正被添加到对象结构中的功能。
  • Element:抽象的元素对象,对象结构的顶层接口,定义接受访问的操作(accept)。
  • ConcreteElement:具体元素对象,对象结构中的具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。
  • ObjectStructure:对象结构,通常包含多个被访问对象,它可以遍历多个被访问的对象,也可以让访问者访问它的元素。可以是一个复合或是一个集合,如一个列表或无序集合。这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素。

说明:

访问者模式仅应当用于数据结构非常稳定的情况下使用,它把数据结构和作用于数据结构上的操作之间的耦合脱开,使得操作集合可以相对自由地演化。
数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。



例子
//访问者角色
interface Visitor {
 void visit(Gladiolus g);
 void visit(Runuculus r);
 void visit(Chrysanthemum c);
}

// The Flower hierarchy cannot be changed:
//元素角色
interface Flower {
 void accept(Visitor v);
}

//以下三个具体元素角色
class Gladiolus implements Flower {
 public void accept(Visitor v) { v.visit(this);}
}

class Runuculus implements Flower {
 public void accept(Visitor v) { v.visit(this);}
}

class Chrysanthemum implements Flower {
 public void accept(Visitor v) { v.visit(this);}
}

// Add the ability to produce a string:
//实现的具体访问者角色

class StringVal implements Visitor {
 String s;
 public String toString() { return s; }
 public void visit(Gladiolus g) {
  s = "Gladiolus";
 }

 public void visit(Runuculus r) {
  s = "Runuculus";
 }

 public void visit(Chrysanthemum c) {
  s = "Chrysanthemum";
 }
}

// Add the ability to do "Bee" activities:
//另一个具体访问者角色

class Bee implements Visitor {
 public void visit(Gladiolus g) {
  System.out.println("Bee and Gladiolus");
 }

 public void visit(Runuculus r) {
  System.out.println("Bee and Runuculus");
 }

 public void visit(Chrysanthemum c) {
  System.out.println("Bee and Chrysanthemum");
 }
}

//这是一个对象生成器
//这不是一个完整的对象结构,这里仅仅是模拟对象结构中的元素

class FlowerGenerator {
 private static Random rand = new Random();
 public static Flower newFlower() {
   switch (rand.nextInt(3)) {
  default:
   case 0: return new Gladiolus(); 
   case 1: return new Runuculus();
   case 2: return new Chrysanthemum();
  }
 }
}

//客户 测试程序

public class BeeAndFlowers extends TestCase {

 /*
  在这里你能看到访问者模式执行的流程:
  首先在客户端先获得一个具体的访问者角色
  遍历对象结构
  对每一个元素调用accept方法,将具体访问者角色传入
  这样就完成了整个过程
 */
 //对象结构角色在这里才 组装 上

 List flowers = new ArrayList();
 public BeeAndFlowers() {
  for(int i = 0; i < 10; i++)
   flowers.add(FlowerGenerator.newFlower());
 }

 Visitor sval ;
 public void test() {
  // It’s almost as if I had a function to
  // produce a Flower string representation:
  //这个地方你可以修改以便使用另外一个具体访问者角色

  sval = new StringVal();
  Iterator it = flowers.iterator();
  while(it.hasNext()) {
   ((Flower)it.next()).accept(sval);
   System.out.println(sval);
  }
 }

 public static void main(String args[]) {
  junit.textui.TestRunner.run(BeeAndFlowers.class);
 }

} 

双重分派
对了,你在上面的例子中体会到双重分派的实现了没有?
首先在客户程序中将具体访问者作为参数传递给具体元素(加亮的地方所示)。这便完成了一次分派。
进入具体元素后,具体元素调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去。具体访问者再根据参数的不同来选择方法来执行(加亮的地方所示)。这便完成了第二次分派。

优缺点及适用情况
  先来看下访问者模式的使用能否避免引言中的痛苦。使用了访问者模式以后,对于原来的类层次增加新的操作,仅仅需要实现一个具体访问者角色就可以了,而不必修改整个类层次。而且这样符合“开闭原则”的要求。而且每个具体的访问者角色都对应于一个相关操作,因此如果一个操作的需求有变,那么仅仅修改一个具体访问者角色,而不用改动整个类层次。

  看来访问者模式确实能够解决我们面临的一些问题。

  而且由于访问者模式为我们的系统多提供了一层“访问者”,因此我们可以在访问者中添加一些对元素角色的额外操作。

  但是“开闭原则”的遵循总是片面的。如果系统中的类层次发生了变化,会对访问者模式产生什么样的影响呢?你必须修改访问者角色和每一个具体访问者角色……

  看来访问者角色不适合具体元素角色经常发生变化的情况。而且访问者角色要执行与元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来,而在java中就意味着其它的对象也可以访问。这就破坏了元素角色的封装性。而且在访问者模式中,元素与访问者之间能够传递的信息有限,这往往也会限制访问者模式的使用。

  《设计模式》一书中给出了访问者模式适用的情况:

  1) 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。

  2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。

  3) 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。

  4) 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。


总结
  这是一个巧妙而且复杂的模式,它的使用条件比较苛刻。当系统中存在着固定的数据结构(比如上面的类层次),而有着不同的行为,那么访问者模式也许是个不错的选择。


特别鸣谢
深入浅出Java的访问者模式访问者模式
访问者模式

访问着模式-刘伟




分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics