本站首页    管理页面    写新日志    退出


«August 2025»
12
3456789
10111213141516
17181920212223
24252627282930
31


公告
 本博客在此声明所有文章均为转摘,只做资料收集使用。

我的分类(专题)

日志更新

最新评论

留言板

链接

Blog信息
blog名称:
日志总数:1304
评论数量:2242
留言数量:5
访问次数:7591582
建立时间:2006年5月29日




[集成测试]访问差异类型的集合类--visitor模式入门
软件技术

lhwork 发表于 2007/1/9 8:52:19

访问差异类型的集合类--visitor模式入门本文对应代码下载这里一,问题提出访问同一类型的集合类是我们最常见的事情了,我们工作中这样的代码太常见了。 1 500)this.width=500'> Iterator ie  =  list.iterator();2 500)this.width=500'> 500)this.width=500'> while (ie.hasNext()) 500)this.width=500'> {3 500)this.width=500'>    Person p  =  (Person)ie.next();4 500)this.width=500'>    p.doWork();5 500)this.width=500'>} 这种访问的特点是集合类中的对象是同一类对象Person,他们拥有功能的方法run,我们调用的恰好是这个共同的方法。在大部份的情况下,这个是可以的,但在一些复杂的情况,如被访问者的继承结构复杂,被访问者的并不是同一类对象,也就是说不是继承自同一个根类。方法名也并不相同。例如Java GUI中的事件就是一个例子。例如这样的问题,有如下类和方法:类:PA ,方法:runPA();类:PB ,方法:runPB();类:PC ,方法:runPC();类:PD ,方法:runPD();类:PE ,方法:runPE();有一个集合类ListList list = new ArrayList();list.add(new PA());list.add(new PB());list.add(new PC());list.add(new PD());list.add(new PE());....二:解决要求能访问到每个类的对应的方法。我们第一反应应该是这样的。  1 500)this.width=500'> Iterator ie  =  list.iterator(); 2 500)this.width=500'> 500)this.width=500'> while (ie.hasNext()) 500)this.width=500'> { 3 500)this.width=500'>    Object obj  =  ie.next(); 4 500)this.width=500'> 500)this.width=500'>     if  (obj  instanceof  PA) 500)this.width=500'> { 5 500)this.width=500'>        ((PA)obj).runPA(); 6 500)this.width=500'> 500)this.width=500'>    } else   if (obj  instanceof  PB) 500)this.width=500'> { 7 500)this.width=500'>        ((PB)obj).runPB(); 8 500)this.width=500'> 500)this.width=500'>    } else   if (obj  instanceof  PC) 500)this.width=500'> { 9 500)this.width=500'>        ((PC)obj).runPC();10 500)this.width=500'> 500)this.width=500'>    } else   if (obj  instanceof  PD) 500)this.width=500'> {11 500)this.width=500'>        ((PD)obj).runPD();12 500)this.width=500'> 500)this.width=500'>    } else   if (obj  instanceof  PE) 500)this.width=500'> {13 500)this.width=500'>        ((PE)obj).runPE();14 500)this.width=500'>    } 15 500)this.width=500'>} 三:新问题及分析解决当数目变多的时候,维护if else是个费力气的事情:仔细分析if,else做的工作,首先判断类型,然後根据类型执行相应的函数如何才能解决这两个问题呢?首先想到的是java的多态,多态就是根据参数执行相应的内容,能很容易的解决第二个问题,我们可以写这样一个类:  1 500)this.width=500'> 500)this.width=500'> public   class  visitor 500)this.width=500'> { 2 500)this.width=500'> 500)this.width=500'>     public   void  run(PA pa) 500)this.width=500'> { 3 500)this.width=500'>        pa.runPA(); 4 500)this.width=500'>    }  5 500)this.width=500'> 500)this.width=500'>     public   void  run(PB pb) 500)this.width=500'> { 6 500)this.width=500'>        pb.runPB(); 7 500)this.width=500'>    }  8 500)this.width=500'> 500)this.width=500'>     public   void  run(PC pc) 500)this.width=500'> { 9 500)this.width=500'>        pc.runPC();10 500)this.width=500'>    } 11 500)this.width=500'> 500)this.width=500'>     public   void  run(PD pd) 500)this.width=500'> {12 500)this.width=500'>        pd.runPD();13 500)this.width=500'>    } 14 500)this.width=500'> 500)this.width=500'>     public   void  run(PE pe) 500)this.width=500'> {15 500)this.width=500'>        pe.runPE();16 500)this.width=500'>    } 17 500)this.width=500'>} 这样只要调用run方法,传入对应的参数就能执行了。还有一个问题就是判断类型。由于重载(overloading)是静态多分配(java语言本身是支持"静态多分配"的。关于这个概念请看这里)所以造成重载只根据传入对象的定义类型,而不是实际的类型,所以必须在传入前就确定类型,这可是个难的问题,因为在容器中对象全是Object,出来后要是判断是什么类型必须用if (xx instanceof xxx)这种方法,如果用这种方法启不是又回到了原点,有没有什么更好的办法呢? 我们知到Java还有另外一个特点,覆写(overriding),而覆写是"动态单分配"的(关于这个概念见这里),那如何利用这个来实现呢?看下边这个方法: 我们让上边的一些类PA PB PC PD PE都实现一个接口P,加入一个方法,accept();  1 500)this.width=500'> 500)this.width=500'> public   void  accept(visitor v) 500)this.width=500'> { 2 500)this.width=500'>     // 把自己传入1  3 500)this.width=500'>     v.run( this ); 4 500)this.width=500'>}  5 500)this.width=500'>然後在visitor中加入一个方法 6 500)this.width=500'> 500)this.width=500'> public   void  run(P p) 500)this.width=500'> { 7 500)this.width=500'>     // 把自己传入2  8 500)this.width=500'>     p.accept( this ); 9 500)this.width=500'>} 10 500)this.width=500'> // 这样你在遍历中可以这样写 11 500)this.width=500'> Visitor v  =   new  Visitor();12 500)this.width=500'>Iterator ie  =  list.iterator();13 500)this.width=500'> 500)this.width=500'> while (ie.hasNext()) 500)this.width=500'> {14 500)this.width=500'>    P p  =  (P)ie.next();15 500)this.width=500'>        p.accept(v);16 500)this.width=500'>    } 17 500)this.width=500'>} 首先执行的是"把自己传入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因为这个顺序更方便一般人理解


阅读全文(2599) | 回复(0) | 编辑 | 精华
 



发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.367 second(s), page refreshed 144776901 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号