大话设计模式
字体: 16 + -

第226章

大b:“静态分派,动态分派,多分派,单分派是visitor模式准备。”

小a:“visitor模式准备?能不能详细讲讲,我不明白。”

大b:“可以。”

1、静态分派:

(1)定义:发生在编译时期,分派根据静态类型信息发生,重载就是静态分派。

(2)什么是静态类型:变量被声明时的类型是静态类型。

什么是动态类型:变量所引用的对象的真实类型。

(3)有两个类,blackcat,whitecat都继承自。

如下调用

classcat{}

classwhitecatextendscat{}

classblackcatextendscat{}

publicclassperson{

publicvoidfeed(catcat){

system.out.println(“feedcat”);

}

publicvoidfeed(whitecatcat){

system.out.println(“feedwhitecat”);

}

publicvoidfeed(blackcatcat){

system.out.println(“feedblackcat”);

}

publicstaticvoidmain(string[]args){

catwc=newwhitecat();

catbc=newblackcat();

personp=newperson();

p.feed(wc);

p.feed(bc);

}

}

运行结果是:

feedcat

feedcat

这样的结果是因为重载是静态分派,在编译器执行的,取决于变量的声明类型,因为wc,bc都是cat所以调用的都是feed(catcat)的函数。

2、动态分派

定义:发生在运行期,动态分派,动态的置换掉某个方法。

还是上面类似的例子:

classcat{

publicvoideat(){

system.out.println(“cateat”);

}

}

publicclassblackcatextendscat{

publicvoideat(){

system.out.println(“blackcateat”);

}

publicstaticvoidmain(string[]args){

catcat=newblackcat();

cat.eat();

}

}这个时候的结果是:

blackcateat

这样的结果是因为在执行期发生了向下转型,就是动态分派了。

3、单分派:

定义:根据一个宗量的类型进行方法的选择。

4、多分派:

(1)定义:根据多于一个宗量的类型对方法的选择。

(2)说明:多分派其实是一系列的单分派组成的,区别的地方就是这些但分派不能分割。

(3)java是动态单分派,静态多分派语言。

大b:“访问同一类型的集合类是我们最常见的事情了,我们工作中这样的代码太常见了。”

iteratorie=list.iterator();

while(ie.hasnext()){

person

p=(person)ie.next();

p.dowork();

}

这种访问的特点是集合类中的对象是同一类对象person,他们拥有功能的方法run,我们调用的恰好是这个共同的方法。在大部份的情况下,这个是可以的,但在一些复杂的情况,如被访问者的继承结构复杂,被访问者的并不是同一类对象,也就是说不是继承自同一个根类。方法名也并不相同。例如javagui中的事件就是一个例子。

例如这样的问题,有如下类和方法:

类:pa,方法:runpa();

类:pb,方法:runpb();

类:pc,方法:runpc();

类:pd,方法:runpd();

类:pe,方法:runpe();

有一个集合类。

listlist=newarraylist();

list.add(newpa());

list.add(newpb());

list.add(newpc());

list.add(newpd());

list.add(newpe());

……

大b:“要求能访问到每个类的对应的方法。我们第一反应应该是这样的。”

iteratorie=list.iterator();

while(ie.hasnext()){

objectobj=ie.next();

if(objinstanceofpa){

((pa)obj).runpa();

}elseif(objinstanceofpb){

((pb)obj).runpb();

}elseif(objinstanceofpc){

((pc)obj).runpc();

}elseif(objinstanceofpd){

((pd)obj).runpd();

}elseif(objinstanceofpe){

((pe)obj).runpe();

}

}

大b:“当数目变多的时候,维护ifelse是个费力气的事情:仔细分析if,else做的工作,首先判断类型,然后根据类型执行相应的函数。”

小a:“如何才能解决这两个问题呢?”

大b:“首先想到的是java的多态,多态就是根据参数执行相应的内容,能很容易的解决第二个问题,我们可以写这样一个类。”

publicclassvisitor{

publicvoidrun(papa){

pa.runpa();

}

publicvoidrun(pbpb){

pb.runpb();

}

publicvoidrun(pcpc){

pc.runpc();

}

publicvoidrun(pdpd){

pd.runpd();

}

publicvoidrun(pepe){

pe.runpe();

}

}

大b:“这样只要调用run方法,传入对应的参数就能执行了。”

小a:“还有一个问题就是判断类型。”

大b:“由于重载(overloading)是静态多分配。java语言本身是支持‘静态多分配’的。所以造成重载只根据传入对象的定义类型,而不是实际的类型,所以必须在传入前就确定类型,这可是个难的问题,因为在容器中对象全是object,出来后要是判断是什么类型必须用if(xxinstanceofxxx)这种方法。”

小a:“如果用这种方法岂不是又回到了原点,有没有什么更好的办法呢?我们知道java还有另外一个特点,覆写(overriding),而覆写是‘动态单分配’的,那如何利用这个来实现呢?”

大b:“看下边这个方法:我们让上边的一些类pa、pb、pc、pd、pe都实现一个接口p,加入一个方法,accept()。”

publicvoidaccept(visitorv){

//把自己传入。

v.run(this);

}

然後在visitor中加入一个方法

publicvoidrun(pp){

//把自己传入。

p.accept(this);

}

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

visitorv=newvisitor();

iteratorie=list.iterator();

while(ie.hasnext()){

pp=(p)ie.next();

p.accept(v);

}

}

大b:“首先执行的是‘把自己传入2’,在这里由于java的特性,实际执行的是子类的accept(),也就是实际类的accept然后是‘把自己传入1’,在这里再次把this传入,就明确类型,ok我们巧妙的利用overriding解决了这个问题。其实归纳一下第二部分,一个关键点是‘自己认识自己’,是不是很可笑。”