深入浅出Java的访问者模式

2016-02-19 14:59 1 1 收藏

生活已是百般艰难,为何不努力一点。下面图老师就给大家分享深入浅出Java的访问者模式,希望可以让热爱学习的朋友们体会到设计的小小的乐趣。

【 tulaoshi.com - 编程语言 】

  一、引子

  对于系统中一个已经完成的类层次结构,我们已经给它提供了满足需求的接口。但是面对新增加的需求,我们应该怎么做呢?如果这是为数不多的几次变动,而且你不用为了一个需求的调整而将整个类层次结构统统地修改一遍,那么直接在原有类层次结构上修改也许是个 不错 的主意。

  但是往往我们遇到的却是:这样的需求变动也许会不停的发生;更重要的是需求的任何变动可能都要让你将整个类层次结构修改个底朝天。这种类似的操作分布在不同的类里面,不是一个好现象,我们要对这个结构重构一下了。

  那么,访问者模式也许是你很好的选择。

  二、定义与结构

  访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的访问者来完成对已有代码功能的提升。

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

  以下是访问者模式的组成结构:

  1) 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。

  2) 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。

  3) 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。

  4) 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。

  5) 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  来张类图就能更加清晰的看清访问者模式的结构了。

  那么像引言中假想的。我们应该做些什么才能让访问者模式跑起来呢?首先我们要在原有的类层次结构中添加accept方法。然后将这个类层次中的类放到一个对象结构中去。这样再去创建访问者角色

  三、举例

  本人阅历实在可怜,没能找到访问者模式在实际应用中的例子。只好借《Thinking in Patterns with java》中的教学代码一用。我稍微做了下修改。

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  

import java.util.*;
import junit.framework.*;
//访问者角色
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) 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

  你是否能很好的理解呢?

  六、总结

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

来源:https://www.tulaoshi.com/n/20160219/1608525.html

延伸阅读
随着计算机网络技术的突飞猛进,网络安全的问题已经日益突出地摆在各类用户的面前。仅从笔者掌握的资料表明,目前在互联网上大约有将近20%以上的用户曾经遭受过黑客的困扰。尽管黑客如此猖獗,但网络安全问题至今仍没有能够引起足够的重视,更多的用户认为网络安全问题离自己尚远,这一点从大约有40%以上的用户特别是企业级用户没有安装防火墙(...
设置MySQL数据同步(单向&双向)由于公司的业务需求,需要网通和电信的数据同步,就做了个MySQL的双向同步,记下过程,以后用得到再翻出来,也贴出来供大家参考。 一、准备服务器 由于MySQL不同版本之间的(二进制日志)binlog格式可能会不一样,因此最好的搭配组合是Master的MySQL版本和Slave的版本相同或者更低,Master的版...
2、用Visual C# 创建Windows应用程序 在Visual C#创建一个Windows (GUI) 应用程序要以前版本的VC++ 容易得多。下面将介绍用Visual C#工程文件向导创建Windows应用程序的过程。 创建应用程序框架 在VS .NET IDE中选择新建-工程文件-Visual C# 工程文件-Windows 应用程序: 然后点击 OK,出现一个表单设计视图(这与VB或Delphi相同)。...
一、什么是 C#? C# 是由Microsoft开发的一种新型编程语言,由于它是从C和C++ 中派生出来的,因此具有C++的功能。同时,由于是Microsoft公司的产品,它又同 VB一样简单。对于web开发而言,C#象 Java,同时具有Delphi的一些优点。Microsoft宣称:C#是开发.NET框架应用程序的最好语言。 二、Java与 C# 要学习C#,不能不首先看一看Java语言。 相...
五、C# 编辑器 编写C#程序可以在文本编辑器中进行,或者在集成开发环境Visual Studio中进行。市场上还有一些第三方编辑器,其中一些是免费的。更多信息请查看这里。 六、C#的程序结构 一个C#程序包含一个类,这个类中至少有一个公用的静态方法Main,这个方法对程序进行初始化并终止程序。在Main方法中创建子对象、执行方法并实现软件的逻辑...

经验教程

54

收藏

51
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部