Java Tip: 用Reflection实现Visitor模式

AutoCAD_LISP操作数据库方法.doc 简述AutoCAD_LISP操作数据库的一般方法; ;生成MS-Access 或 MS-SQL Server 数据库的连接字符串 立即下载

Java Tip: 用Reflection实现Visitor模式

概述

Visitor模式的常用之处在于,它将对象集合的结构和对集合所执行的操作分离开来。例如,它可以将一个编译器中的分析逻辑和代码生成逻辑分离开来。有了这样的分离,想使用不同的代码生成器就会很容易。更大的好处还有,其它一些公用程序,如lint,可以在使用分析逻辑的同时免受代码生成逻辑之累。不幸的是,向集合中增加新的对象往往需要修改已经写好的Visitor类。本文提出了一种在Java中实现Visitor模式的更灵活的方法:使用Reflection(反射)。

-------------------------------------------------------------

集合(Collection)普遍应用于面向对象编程中,但它也经常引发一些和代码有关的疑问。例如,"如果一个集合存在不同的对象,该如何对它执行操作?"

一种方法是,对集合中的每个元素进行迭代,然后基于所在的类,对每个元素分别执行对应的操作。这会很难办,特别是,如果你不知道集合中有什么类型的对象。例如,假设想打印集合中的元素,你可以写出如下的一个方法(method):

public void messyPrintCollection(Collection collection) {
   Iterator iterator = collection.iterator()
   while (iterator.hasNext())
      System.out.println(iterator.next().toString())
}

这看起来够简单的了。它只不过调用了Object.toString()方法,然后打印出对象,对吗?但如果有一组哈希表怎么办?事情就会开始变得复杂起来。你必须检查从集合中返回的对象的类型:

public void messyPrintCollection(Collection collection) {
   Iterator iterator = collection.iterator()
   while (iterator.hasNext()) {
      Object o = iterator.next();
      if (o instanceof Collection)
         messyPrintCollection((Collection)o);
      else
         System.out.println(o.toString());
   }
}

不错,现在已经解决了嵌套集合的问题,但它需要对象返回String,如果有其它不返回String的对象存在怎么办?如果想在String对象前后添加引号以及在Float后添加f又该怎么办?代码还是越来越复杂:

public void messyPrintCollection(Collection collection) {
   Iterator iterator = collection.iterator()
   while (iterator.hasNext()) {
      Object o = iterator.next();
      if (o instanceof Collection)
         messyPrintCollection((Collection)o);
      else if (o instanceof String)
         System.out.println("'"+o.toString()+"'");
      else if (o instanceof Float)
         System.out.println(o.toString()+"f");
      else
         System.out.println(o.toString());
   }
}

可以看到,事情的复杂度会急剧增长。你当然不想让一段代码到处充斥着if-else语句!那怎么避免呢?Visitor模式可以帮你。

要实现Visitor模式,得为访问者建立一个Visitor接口,还要为被访问的集合建立一个Visitable接口。然后,让具体类实现Visitor和Visitable接口。这两个接口如下所示:

public interface Visitor
{
   public void visitCollection(Collection collection);
   public void visitString(String string);
   public void visitFloat(Float float);
}

public interface Visitable
{
   public void accept(Visitor visitor);
}

对于具体的String,可能是这样:

public class VisitableString implements Visitable
{
   private String value;
   public VisitableString(String string) {
      value = string;
   }
   public void accept(Visitor visitor) {
      visitor.visitString(this);
   }
}

在accept方法中,对this类型调用正确的visitor方法:

visitor.visitString(this)

这样,就可以如下实现具体的Visitor:

public class PrintVisitor implements Visitor
{
   public void visitCollection(Collection collection) {
      Iterator iterator = collection.iterator()
      while (iterator.hasNext()) {
      Object o = iterator.next();
      if (o instanceof Visitable)
         ((Visitable)o).accept(this);
   }

   public void visitString(String string) {
      System.out.println("'"+string+"'");
   }

   public void visitFloat(Float float) {
      System.out.println(float.toString()+"f");
   }
}

实现VisitableFloat和VisitableCollection类的时候,它们也是各自调用合适的Visitor方法,所得到的效果和前面那个用了if-else的messyPrintCollection方法一样,但这里的手法更干净。在visitCollection()中,调用的是Visitable.accept(this),然后这个调用又返回去调用一个合适的Visitor方法。这被称做 "双分派";即,Visitor先调用了Visitable类中的方法,这个方法又回调到Visitor类中。

虽然通过实现visitor消除了if-else语句,却也增加了很多额外的代码。最初的String和Float对象都要用实现了Visitable接口的对象进行包装。这有点讨厌,但一般说来不是问题,因为你可以让经常被访问的集合只包含那些实现了Visitable接口的对象。

但似乎这还是额外的工作。更糟糕的是,当增加一个新的Visitable类型如VisitableInteger时,会发生什么呢?这是Visitor模式的一个重大缺陷。如果想增加一个新的Visitable对象,就必须修改Visitor接口,然后对每一个Visitor实现类中的相应的方法一一实现。你可以用一个带缺省空操作的Visitor抽象基类来代替接口。那就很象Java GUI中的Adapter类。那个方法的问题在于,它需要占用单继承;而你往往想保留单继承,让它用于其它什么东西,比如继承StringWriter。那个方法还有限制,它只能够成功访问Visitable对象。

幸运的是,Java可以让Visitor模式更灵活,使得你可以随心所欲地增加Visitable对象。怎么做?答案是,使用Reflection。比如,可以设计这样一个ReflectiveVisitor接口,它只需要一个方法:

public interface ReflectiveVisitor {
   public void visit(Object o);
}

就这样,很简单。至于Visitable,还是和前面一样,我过一会儿再说。现在先用Reflection来实现PrintVisitor:

public class PrintVisitor implements ReflectiveVisitor {
   public void visitCollection(Collection collection)
   { ... same as above ... }
   public void visitString(String string)
   { ... same as above ... }
   public void visitFloat(Float float)
   { ... same as above ... }

   public void default(Object o)
   {
      System.out.println(o.toString());
   }

   public void visit(Object o) {
      // Class.getName() returns package information as well.
      // This strips off the package information giving us
      // just the class name
      String methodName = o.getClass().getName();
      methodName = "visit"+
                   methodName.substring(methodName.lastIndexOf('.')+1);
      // Now we try to invoke the method visit
      try {
         // Get the method visitFoo(Foo foo)
         Method m = getClass().getMethod(methodName,
            new Class[] { o.getClass() });
         // Try to invoke visitFoo(Foo foo)
         m.invoke(this, new Object[] { o });
      } catch (NoSuchMethodException e) {
         // No method, so do the default implementation
         default(o);
      }
   }
}

现在不需要Visitable包装类。仅仅只是调用visit(),请求就会分发到正确的方法上。很不错的一点是,只要认为适合,visit()就可以分发。这并非必须使用reflection--它可以使用其它完全不同的机制。

新的PrintVisitor中,有针对Collection,String和Float而写的方法,但然后它又在catch语句中捕捉所有未处理的类型。你要扩展visit()方法,使得它也能够处理所有的父类。首先,得增加一个新方法,称为getMethod(Class c),它返回的是要调用的方法;为了找到这个相匹配的方法,先在类c的所有父类中寻找,然后在类c的所有接口中寻找。

protected Method getMethod(Class c) {
   Class newc = c;
   Method m = null;
   // Try the superclasses
   while (m == null && newc != Object.class) {
      String method = newc.getName();
      method = "visit" + method.substring(method.lastIndexOf('.') + 1);
      try {
         m = getClass().getMethod(method, new Class[] {newc});
      } catch (NoSuchMethodException e) {
         newc = newc.getSuperclass();
      }
   }
   // Try the interfaces.  If necessary, you
   // can sort them first to define 'visitable' interface wins
   // in case an object implements more than one.
   if (newc == Object.class) {
      Class[] interfaces = c.getInterfaces();
      for (int i = 0; i < interfaces.length; i++) {
         String method = interfaces[i].getName();
         method = "visit" + method.substring(method.lastIndexOf('.') + 1);
         try {
            m = getClass().getMethod(method, new Class[] {interfaces[i]});
         } catch (NoSuchMethodException e) {}
      }
   }
   if (m == null) {
      try {
         m = thisclass.getMethod("visitObject", new Class[] {Object.class});
      } catch (Exception e) {
          // Can't happen
      }
   }
   return m;
}

看起来有些复杂,其实不然。实际上,它只是根据传进来的类名去寻找相应的方法而已。如果没找到,就在父类中找;还没找到,再到接口中找。最后,就拿visitObject()作为缺省。

注意,为了照顾那些熟悉传统Visitor模式的读者,我对方法的名称采用了传统的命名方式。但正如你们一些人所注意到的,把所有的方法命名为 "visit" 然后让参数类型作为区分会更高效。但这样做的话,你得把主visit(Object o)方法的名字改为dispatch(Object o)之类。否则,就没有一个缺省方法可用了,你就得在调用visit(Object o)时将类型转换为Object,以保证visit采用的是正确的调用方式。

现在可以修改visit()方法,以利用getMethod():

public void visit(Object object) {
   try {
     Method method = getMethod(getClass(), object.getClass());
     method.invoke(this, new Object[] {object});
   } catch (Exception e) { }
}

现在,visitor对象的功能强大多了。你可以传进任何对象,并且有某个方法处理它。另外一个好处是,还有一个缺省方法visitObject(Object o),它可以捕捉任何未知的对象。再多花点工夫,你还可以写出一个visitNull()方法。

我在上面对Visitable接口避而不谈自有原因。传统Visitor模式的另一个好处是,它允许Visitable对象来控制对对象结构的访问。例如,假设有一个实现了Visitable的TreeNode对象,你可以让一个accept()方法来遍历它的左右节点:

public void accept(Visitor visitor) {
   visitor.visitTreeNode(this);
   visitor.visitTreeNode(leftsubtree);
   visitor.visitTreeNode(rightsubtree);
}

这样,只用对Visitor类再进行一点修改,就可以进行Visitable控制访问:

public void visit(Object object) throws Exception
{
    Method method = getMethod(getClass(), object.getClass());
     method.invoke(this, new Object[] {object});
     if (object instanceof Visitable)
     {
          callAccept((Visitable) object);
     }
}
public void callAccept(Visitable visitable) {
   visitable.accept(this);
}

如果已经实现了一个Visitable对象结构,可以保留callAccept()方法并使用Visitable控制访问。如果想在visitor中访问结构,只需改写callAccept()方法,使之什么也不做。

想让数个不同的访问者对同一个对象集合进行访问时,Visitor模式可以发挥它的强大作用。假设已经有一个解释器,一个中缀写作器,一个后缀写作器,一个XML写作器和一个SQL写作器,它们都作用在同一个对象集合上。那么,也可以很容易地为相同的对象集合写出一个前缀写作器和一个SOAP写作器。另外,这些写作器可以正常地和它们所不知道的对象工作;当然,如果愿意,也可以让它们抛出异常。

结论
通过使用Java Reflection,你可以增强Visitor模式,使之具有操作对象结构的强大功能,并在增加新Visitable类型方面提供灵活性。希望你在以后的程序设计中能够应用这一模式。

当今主流的所有网络编程语言 当今主流的所有网络编程语言 介绍!!!!!!!! 立即下载

相关推荐

LPDDR4x 深度解析(3) - SDRAM核心操作机制

本文深入解析LPDDR4x SDRAM的核心操作机制,包括基础架构、刷新操作、读写流程及低功耗设计。详细介绍了存储阵列、地址解码器等关键模块,以及自刷新与自动刷新模式的区别。通过实测数据和技术细节,揭示LPDDR4x在性能优化和功耗控制方面的创新设计,为硬件工程师提供实用参考。

weixin_29278805的博客 308

lisp连接mysql_Common Lisp 操作Mysql

Common Lisp 通过CFFI可以调用其它语言的接口,如此,Common Lisp可以快速开发各种应用程序,本文将讲述在ubuntu系统下的一个简单的Common Lisp与mysql交互的实例。准备安装CFFIsudo apt-get install cl-cffi安装CL-MYSQLsudo apt-get install cl-sql-mysql安装MYSQLsudo apt-get ...

weixin_42144201的博客 441

轴承寿命预测实战:手把手教你用IEEE PHM2012数据集训练模型(附Python代码)

本文详细介绍了如何利用IEEE PHM2012数据集构建轴承剩余寿命预测模型,从数据预处理、特征工程到LSTM模型训练与部署,提供了完整的Python代码实现。通过实战案例,帮助读者掌握预测性维护中的关键技术,提升工业设备的运行安全与效率。

weixin_29186249的博客 312

大模型Agent设计模式详解:Reflection模式让AI学会自我反思与改进!

文章介绍了Reflection(反思)模式,这是一种让AI Agent具备自我评估和自我改进能力的设计模式。通过"生成初步结果→反思评估→优化改进→再次生成→..."的循环流程,有效提升执行结果准确率。文章以编程场景为例,使用LangGraph构建了包含Code Node、Reflection Node和Conditional Edge的Reflection Agent,通过多轮反思与优化迭代,实现代码自动优化。该模式类似于程序员的开发流程,可显著提升代码质量。

2401_85375151的博客 1478

强化学习:Distributed PPO (DPPO) 学习笔记

DPPO = Distributed + Proximal Policy Optimization Distributed:多个智能体并行训练 Proximal:限制策略更新幅度,确保稳定性 Policy Optimization:基于策略梯度的优化方法 核心思想:多个智能体并行探索环境,共享经验,通过裁剪策略更新确保训练稳定

只是个人学习笔记 1332

timing window file中的S L T指什么意思

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?

m0_61544122的博客 776

用Lisp语言编写监控局域网上网记录的软件:符号计算与元编程

在网络安全日益重要的今天,监控和记录局域网上网活动是确保信息安全的一项重要任务。本文将介绍如何使用Lisp语言编写一个简单而强大的监控工具,通过符号计算和元编程实现对局域网上网记录的有效追踪。

Yao_0211的博客 184

RK3568平台(USB篇)USB HUB集线器

hub,集线器,连接在host与device之间的一种用于usb接口扩展的usb设备。可以将一个usb上行接口扩展为多个下行接口,使得一个host可以同时与多个device连接。一般来说,一块hub桥接芯片可扩展4个usb接口,而市面上的一拖七hub,其实使用了两块hub桥接芯片,其中一块hub桥接芯片的上行端口连接到另一块hub桥接芯片的下行端口。hub的上行端口面向host,下行端口面向device。在下行端口上,hub提供了设备接入检测和设备移除检测的能力,并给下行端口供电。

weixin_49303682的博客 964

lisp连接mysql_lisp语句操作ACCESS数据库的例子

lisp语句操作ACCESS数据库的例子;(setq con (tls-ado-opencon (strcat (vla-get-path(vlax-get-acad-object)) "/tlscad/bin/house.mdb")));(tls-ado-runtrans con "insert into houseinfovalues(´1´,´2´,´0001´,67,´zh´,23,´lz...

weixin_30336531的博客 726

【K8S系列】Kubernetes 中 Service 的流量不均匀问题【已解决】

在 Kubernetes 中,Service 是一种抽象,用于定义一组 Pod 的访问策略。当某些 Pod 接收的流量过多,而其他 Pod 的流量较少时,可能会导致负载不均衡。这种情况不仅影响性能,还可能导致某些 Pod 过载,影响应用的可用性。本文将详细分析此问题的原因及其解决方案。

颜淡慕潇 8740

27930-2023与27930-2015区别

描述了27930-2023与27930-2015的更新点

manjixingkong1234的专栏 3850

吴恩达亲授!AI反思模式全解析:大模型自我优化20%的终极密码

2025 年,随着大语言模型 (LLM) 技术的持续突破,AI Agent 已经成为大模型从实验室概念成为迈向企业级应用的关键转折点。

m0_59163425的博客 1504

【STM32系统及仿真】基于STM32+蓝牙设计的智能窗帘系统__资料编号8

本项目设计并实现了一种基于STM32单片机的智能窗帘系统,集成了光敏传感器、温湿度传感器、蓝牙模块和OLED显示屏,实现了窗帘的自动调节、环境监测和远程控制功能。系统采用STM32F103C8T6作为主控制器,通过光敏传感器检测环境光强,温湿度传感器监测室内环境,蓝牙模块实现与手机APP的通信,用户可以通过APP远程控制窗帘的开合状态。系统在检测到环境光照强度或温度超出设定范围时,可自动调节窗帘的开合,以达到节能和提高舒适度的目的。

嵌入式阿齐Archie 2万+

Java Tip: 用Reflection实现Visitor模式 (转)

Java Tip: 用Reflection实现Visitor模式 (转)[@more@]Java Tip: 用Reflection实现Visitor模式 概述 Visitor模式的常用之处在于,它将对象集合的结构和对集合所执...

csd3176的博客 126

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Lisp语言的数据库交互

Lisp是一种古老而强大的编程语言,诞生于1958年,其设计理念注重语法的简洁性和灵活性。Lisp语言中最具特色的部分是其代码和数据的同构特性,即Lisp程序的代码本身也是Lisp的对象。这种特性使得Lisp非常适合用于快速开发原型、自动化生成代码和构建复杂的系统。Lisp的许多方言中,Common Lisp和Scheme是最为知名的。Common Lisp是一种多范式的语言,支持面向对象、函数式和命令式编程。而Scheme以其简约的语法受到广泛喜爱,特别是在学术界和教育领域。

2501_91226542的博客 350

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

5种Agentic设计模式:langchain实现:反思模式、工具模式、ReAct 、规划模式(Planning Pattern ) 、Multi-Agent Pattern

LLM 不再仅仅依赖其内部知识,而是利用Tool查询数据库、调用 API 和执行 Python 脚本,调用外部知识完成工作。让LLM将任务分解成更小、容易实现的子目标,并且概述目标,就像项目经理组织复杂的工作流程一样。在这种模式下,Agent会“审查”自己的工作——发现错误并不断迭代,直到最终结果完美无缺。将使用Tool的agent看做是一个人,LLM 是它的大脑,一套Tool是它采取行动的手。Agent利用Reflection通过 LLM 生成的输出,以及使用Tool与外部世界交互。

具身机器人和通用AI 2189

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

网络编程那些事

引言 经常有人问我同样的问题:动态网站是如何工作的,开发它都需要掌握哪些开发技术,使用哪一门脚本语言最合适等。希望借此机会就这些问题做出回答,使大家对网络,对网络编程,网站建设有一个大致的了解。 1.网络编程的起源与意义 短短数年之间,Web已成为我们生活中非常熟悉的东西,没有它,生活简直难以想象。 随着对浏览器及书签的熟悉,我们对于Web的历史也略知一二。 从默默无闻到一鸣惊人 出现

孤狼 2226

应用设计模式之策略模式(Reflection)

前两篇文章介绍了结合静态代理和简单工厂对策略进行应用,这里介绍使用反射方式应用策略模式。 定义反射策略类: /** * @author alex * */ public class StratagemReflection { private AbstractDao dao; public StratagemReflection(String classN...

灰辉 359

在访问者模式中使用反射

本文由 ImportNew - 文 学敏 翻译自 javaworld。欢迎加入Java小组。转载请参见文章末尾的要求。 集合类型在面向对象编程中很常用,这也带来一些代码相关的问题。比如,“怎么操作集合中不同类型的对象?” 一种做法就是遍历集合中的每个元素,然后根据它的类型而做具体的操作。这会很复杂,尤其当你不知道集合中元素的类型时。如果y要打印集合中的元素,可以写一个这样的方法:

Mr__fang的专栏 902

common lisp 利用cffi 操作mysql

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33979203的博客 222

Lisp介绍之七个原始操作符

http://developer.51cto.com/art/200909/149490.htm 本文节选自《Lisp之根源》一文,描述了Lisp中的七个原始操作符。我们不仅要学习某个人四十年前得出的有趣理论结果,而且展示编程语言的发展方向。 本文是paul graham所著的《Lisp之根源》的第一部分,在对Lisp的介绍中,描述了Lisp中的七个原始操作符。这是一篇非常

wuxianglonghaohao的专栏 2618

Common Lisp 操作Mysql

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33971205的博客 317

Java Tip: 实现Command模式

 Java Tip: 实现Command模式概述有时需要向对象发送请求,但是不知道 "被请求的操作" 或 "请求的接受者" 的任何信息。在面向过程的程序设计语言中,这类通信是通过回调函数来完成的:在某个地方登记这个函数,然后在后面调用它。在面向对象程序中,command(命令)与回调函数等价,它封装了回调函数。本文演示如何在Java中实现Command模式。--------------

lostmouse的专栏 2049
上一篇: Java Q&A: 使用Observer模式
下一篇: Java Q&A: 使用Factory Method模式
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值