Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 348|回复: 0

[设计模式学习]访问差异类型的集合类:visitor模式入门

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-28 23:57:14 | 显示全部楼层 |阅读模式
    一,问题提出
                                                                                        
         访问同一类型的集合类是我们最常见的事情了,我们工作中这样的代码太常见了。
      
       
        1
       
        Iterator ie  
        =
         list.iterator();

        2
       
        while
        (ie.hasNext())
       
        {
    3     Person p  =  (Person)ie.next();
    4     p.doWork();
    5 }
             这种访问的特点是集合类中的对象是同一类对象Person,他们拥有功能的方法run,我们调用的恰好是这个共同的方法。
    在大部份的情况下,这个是可以的,但在一些复杂的情况,如被访问者的继承结构复杂,被访问者的并不是同一类对象,
    也就是说不是继承自同一个根类。方法名也并不相同。例如java GUI中的事件就是一个例子。 例如这样的问题,有如下类和方法:
      
      
       类:PA ,方法:runPA();
    类:PB ,方法:runPB();
    类:PC ,方法:runPC();
    类:PD ,方法:runPD();
    类:PE ,方法:runPE();
    有一个集合类List
    List list = new ArrayList();
    list.add(new PA());
    list.add(new PB());
    list.add(new PC());
    list.add(new PD());
    list.add(new PE());
    ....
    二:解决
    要求能访问到每个类的对应的方法。我们第一反应应该是这样的。
      
       
         1
       
        Iterator ie  
        =
         list.iterator();

         2
       
        while
        (ie.hasNext())
       
        {
      3     Object obj  =  ie.next();
      4      if  (obj  instanceof  PA) {
      5         ((PA)obj).runPA();
      6     } else   if (obj  instanceof  PB) {
      7         ((PB)obj).runPB();
      8     } else   if (obj  instanceof  PC) {
      9         ((PC)obj).runPC();
    10     } else   if (obj  instanceof  PD) {
    11         ((PD)obj).runPD();
    12     } else   if (obj  instanceof  PE) {
    13         ((PE)obj).runPE();
    14     }
    15 }
       
    三:新问题及分析解决
    当数目变多的时候,维护if else是个费力气的事情:
    仔细分析if,else做的工作,首先判断类型,然後根据类型执行相应的函数
    如何才能解决这两个问题呢?首先想到的是java的多态,多态就是根据参数执行相应的内容,
    能很容易的解决第二个问题,我们可以写这样一个类:
      
       
         1
       
       
        public
          
        class
         visitor
       
        {
      2      public   void  run(PA pa) {
      3         pa.runPA();
      4     }
      5      public   void  run(PB pb) {
      6         pb.runPB();
      7     }
      8      public   void  run(PC pc) {
      9         pc.runPC();
    10     }
    11      public   void  run(PD pd) {
    12         pd.runPD();
    13     }
    14      public   void  run(PE pe) {
    15         pe.runPE();
    16     }
    17 }
       
    这样只要调用run方法,传入对应的参数就能执行了。
    还有一个问题就是判断类型。由于重载(overloading)是静态多分配(java语言本身是支持"静态多分配"的。
    关于这个概念请看这里)所以造成重载只根据传入对象的定义类型,而不是实际的类型,所以必须在传入前就确定类型,
    这可是个难的问题,因为在容器中对象全是Object,出来后要是判断是什么类型必须用
    if (xx instanceof xxx)这种方法,如果用这种方法启不是又回到了原点,有没有什么更好的办法呢? 我们知到Java还有另外一个特点,覆写(overriding),而覆写是"动态单分配"的, 那如何利用这个来实现呢?看下边这个方法:
      我们让上边的一些类PA PB PC PD PE都实现一个接口P,加入一个方法,accept();(如果不清楚,请下载源码分析)
      
       
         1
       
       
        public
          
        void
         accept(visitor v)
       
        {
      2      // 把自己传入1
      3      v.run( this );
      4 }
       

         5
        然後在visitor中加入一个方法

         6
       
        public
          
        void
         run(P p)
       
        {
      7      // 把自己传入2
      8      p.accept( this );
      9 }
       

        10
       
        //
        这样你在遍历中可以这样写
       

        11
       
        Visitor v  
        =
          
        new
         Visitor();

        12
        Iterator ie  
        =
         list.iterator();

        13
       
        while
        (ie.hasNext())
       
        {
    14     P p  =  (P)ie.next();
    15         p.accept(v);
    16     }
       

        17
        }
       
    首先执行的是"把自己传入2",在这里由于Java的特性,实际执行的是子类的accept(),也就是实际类的accept
    然後是"把自己传入1",在这里再次把this传入,就明确类型,ok我们巧妙的利用overriding解决了这个问题
    其实归纳一下第二部分,一个关键点是"自己认识自己",是不是很可笑。
    其实在计算计技术领域的很多技术上看起来很高深的东西,其实就是现有社会中人的生活方式的一种映射
    而且这种方式是简单的不能再简单的方式。上边的全部过程基本上是一个简单的visitor模式实现,visitor模式
    已经是设计模式中比较复杂的模式了,但其实原理简单到你想笑。看看下边这个比喻也许你的理解会更深刻。 四:一个帮助理解的比喻:
    题目:指挥工人工作
    条件:你有10个全能工人,10样相同工作。
    需求:做完工作
    实现:大喊一声所有人去工作 条件变了,工人不是全能,但是工作相同,ok问题不大
    条件再变,工作不是相同,但工人是全能,ok问题不大 以上三种情况在现实生活中是很少发生得,最多的情况是这样:

    10个工人,每人会做一种工作,10样工作。你又一份名单Collection)写着谁做什么。但你不认识任何人
    这个时候你怎么指挥呢,方案一:
    你可以一个个的叫工人,然後问他们名字,认识他们,查名单,告诉他们做什么工作。
    你可以直接叫出他们名字,告诉他们干什么,不需要知到他是谁。

    看起来很简单。但如果你要指挥10万人呢 ?而且人员是流动的,每天的人不同,你每天拿到一张文档。
    其实很简单,最常用的做法是,你把这份名单贴在墙上,然後大喊一声,所有人按照去看,按照自己的分配情况去做。

    这里利用的关键点是"所有工人自己认识自己",你不能苛求每个工人会做所有工作,不能苛求所有工作相同,但你能要求所有工人都认识自己。 再想想我们开始的程序,每个工人对应着PA PB PC PD PE....
    所有的工人都是工人P
    每个工人会做的东西不一样runPA runPB runPC
    你有一份名单Visitor(重载)记录着谁做什么工作。 看完上边这些,你是不是会产生如下的问题:
    问题:为什么不把这些方法的方法名做成一样的,那就可以解决了。
    例如,我们每个PA ,PB ,PC都加入一个run 方法,然後run内部再调用自己对应的runPx()方法。
    答案:有些时候从不同的角度考虑,或者因为实现的复杂度早成很难统一方法名。
    例如上边指挥人工作的例子,其实run方法就是大叫一声去工作,因为每个工人只会做一种工作,所以能行, 但我们不能要求所有人只能会做一种事情,这个要求很愚蠢。所以如果每个工人会干两种或者多种工作呢,
    也就是我PA 有runPA() walkPA()等等方法, PB有runPB() climbPB()等等。。。 这个时候按照名单做事才是最好的办法。 五:作者的话
         所以说模式中很多复杂的东西,在现实中其实是很基本的东西,多多代入代出能帮助理解模式。看完本文,如果你对visitor模式有更多的兴趣,想了解更多请看如下几篇文章。

    1,静态分派,动态分派,多分派,单分派 --------------   visitor模式准备

    2,访问差异类型的集合类 ------------------------   visitor模式入门(本文)

    3,visitor模式理论及学术概念-------------------   visitor模式深入

    4,重载overloading和覆写overriding哪个更早执行--   visitor帮助篇
    虽然排列顺序是1,2,3,4 但是我个人建议的学习方式是2,1,3,4因为这个顺序更方便一般人理解  
      


    源码下载:http://file.javaxxz.com/2014/10/28/235714531.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-19 10:55 , Processed in 0.390322 second(s), 46 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表