百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

阿里P7架构师深入浅出Java的访问者模式

yuyutoo 2024-10-20 13:10 1 浏览 0 评论

一、引子

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

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

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

二、定义与结构

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

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

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

  1. 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
  2. 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。
  3. 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。
  4. 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。
  5. 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。

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

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

三、举例

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

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

你是否能很好的理解呢?

六、总结

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

Java架构师丨苏先生:专注于Java开发技术的研究与知识分享!

————END————

  • 点赞(感谢)
  • ...
  • 转发(感谢)
  • ...
  • 关注(感谢)
  • ...

【推荐阅读】

Java程序员备战“金九银十”必备的面试技巧(附携程Java岗面试题)

字节跳动Java研发面试99题(含答案):JVM+Spring+MySQL+线程池+锁

相关推荐

jQuery VS AngularJS 你更钟爱哪个?

在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...

Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0

在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...

如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例

要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...

编程技巧:Jquery实时验证,指定长度的「负小数」

为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...

一篇文章带你用jquery mobile设计颜色拾取器

【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...

编程技巧:Jquery实时验证,指定长度的「正小数」

为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...

jquery.validate检查数组全部验证

问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...

Vue进阶(幺叁肆):npm查看包版本信息

第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...

layui中使用lay-verify进行条件校验

一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...

jQuery是什么?如何使用? jquery是什么功能组件

jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...

django框架的表单form的理解和用法-9

表单呈现...

jquery对上传文件的检测判断 jquery实现文件上传

总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...

Nodejs之MEAN栈开发(四)-- form验证及图片上传

这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...

大数据开发基础之JAVA jquery 大数据java实战

上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...

推荐四个开源的jQuery可视化表单设计器

jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...

取消回复欢迎 发表评论: