访问差异类型的集合类-凯发k8网页登录

开发出高质量的系统

常用链接

统计

积分与排名

好友之家

最新评论

访问差异类型的集合类--visitor模式入门

访问差异类型的集合类--visitor模式入门
本文对应代码下载
一,问题提出
访问同一类型的集合类是我们最常见的事情了,我们工作中这样的代码太常见了。

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,2,访问差异类型的集合类 ------------------------   visitor模式入门(本文)

4,虽然排列顺序是1,2,3,4 但是我个人建议的学习方式是2,1,3,4因为这个顺序更方便一般人理解

 

 

 

 

 

 

 

 

 

 


 

posted on 2006-12-18 20:18 dreamstone 阅读(4873) 评论(14)  编辑  收藏 所属分类: 设计模式

# re: 访问差异类型的集合类--visitor模式入门 2006-12-19 09:37

我有一个疑问,下面这段代码是不是少写了什么东西?我怎么觉得p.accept与v.run之间是死循环呢?请解释一下
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 }   回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-19 09:46

首先执行的是"把自己传入2",???
应该是"把自己传入1"吧.

  回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-19 21:53 dreamstone

to hannyu :
其实这就是visitor模式关键的地方,p.accept(v)的时候会发生向下转型,所以执行的是子类的accept()方法,而子类的accept()方法中,传入this就是子类而不是父类了。也就是说pa中调用v.run(this)其实传入的this是pa类型,而不是p类型了,这样visitor重载的时候就会执行run(pa pa)而不是run(p p),所以不会死循环的。代码在公司,明天把代码上传,你执行一下就知道了。
(上边就是说的自己认识自己了)  回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-19 21:56 dreamstone

to themax :
可能我写的有点歧意,我上边的“把自己传入2";是个label,相当于姓名一,姓名二,下边解释的是拿它来当label用,而不是把自己传入给第二个,sorry。  回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-19 22:31 dreamstone

另外,加入了source的下载,见文章开始,虽然简单,但是可以看看。
个人感觉visitor模式是模式中比较复杂的。实现起来也是比较巧的.两次this的利用。好像有一种学术的叫法"返还球"  回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-20 09:22

多谢,看了源代码终于明白了,visitor模式绕来绕去的真难理解。接口p应该只起来一个辅助作用,如果有个特殊点的命名就容易理解一些了。比如visitee?  回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-27 13:07

我觉得把visitor中

public void run(person p)
{
p.accept(this);
}

去掉也可以运行,
因为在 p.accept(v); 时父类向下转型,执行子类的accept(visitor v),
然后就执行子类在visitor中对应的run()了,和上面那段没有关系阿。

楼主出来解释下。  回复     

# re: 访问差异类型的集合类--visitor模式入门 2006-12-27 19:52

呵呵,其实我当时写这个是为了这样的情况,可能这种情况用的更多
visitor v = new visitor();
iterator ie = list.iterator();
while (ie.hasnext()) {
p p = (p)ie.next();
v.run(p);
}
这个时候那个函数就必须有了。因为run(p)的时候必须在编译器有一个对应的函数,这两个的区别是看想问题的角度了。
是让visitor 访问每一个p
还是让每一个p运行。
不过更多情况应该是v.run(p);更复合思考的习惯。后来发文的时候简化了一下。


  回复     

# re: 访问差异类型的集合类--visitor模式入门[未登录] 2007-02-09 11:52

代码中

for(int i=0;i p p = (p)list.get(i);
p.accept(v);
}

这种方式严重影响性能!!

改成:

p p =null;
for(int i=0;i p = (p)list.get(i);
p.accept(v);
}
  回复     

# re: 访问差异类型的集合类--visitor模式入门 2007-02-28 12:17 dreamstone

to xmlspy:
建议回文的时候调查一下,你的说法是很错误的:具体说明见这里
  回复     

# re: 访问差异类型的集合类--visitor模式入门 2007-07-05 10:32

通过抽象类可以非常方便的实现,也就是“自己找自己”

//抽象类 p
public abstract class p{
public abstract string run();
}

//类pa继承抽象类p
public class pa entends p{
public string run(){
return "pb";
}
}
//类pb继承抽象类p
public class pb entends p{
public string run(){
return "pb";
}
}
//类pc继承抽象类p
public class pc entends p{
public string run(){
return "pc";
}
}
//类pd继承抽象类p
public class pd entends p{
public string run(){
return "pd";
}
}
//类pe继承抽象类p
public class pe entends p{
public string run(){
return "pe";
}
}


list list = new arraylist();
list.add(new pa());
list.add(new pb());
list.add(new pc());
list.add(new pd());
list.add(new pe());


iterator ie = list.iterator();
while (ie.hasnext()) {
run p = (run)ie.next();
p.run();
}   回复     

# re: 访问差异类型的集合类--visitor模式入门 2007-07-05 11:21

iterator ie = list.iterator();
while (ie.hasnext()) {
run p = (run)ie.next();
p.run();
} 应该是

iterator ie = list.iterator();
while (ie.hasnext()) {
p p = (p)ie.next();
p.run();
}   回复     

# re: 访问差异类型的集合类--visitor模式入门 2007-07-06 12:58 dreamstone

@xnabx
你根本不理解什么是visitor啊。
你的那种实现方式是:
调用一个集合类中,不同的对像的"同一个"方法。
我的实现是:
调用一个集合类中,不同的对象的"不同的"方法

另外你曲解了demo的含义,我并不是未了打印出pa,pb,pc,pd只是为了直观那么些。这有点像我问到美国怎么走,你告诉我做300能上三环

至于为什么需要这种需求,最开始写了个简单的引入,如果你开发过很复杂的系统你就会明白这种需求是经常存在的,特别是你在改造一个有很多已有模块的工程。  回复     

# re: 访问差异类型的集合类--visitor模式入门 2010-01-03 17:02

解释的太牵强了,误人子弟  回复     


只有注册用户后才能发表评论。


网站导航:
              
 
网站地图