訪問(wèn)差異類(lèi)型的集合類(lèi)--visitor模式入門(mén)
本文對(duì)應(yīng)代碼下載這里
一,問(wèn)題提出
訪問(wèn)同一類(lèi)型的集合類(lèi)是我們最常見(jiàn)的事情了,我們工作中這樣的代碼太常見(jiàn)了。
1
Iterator ie = list.iterator();
2
while (ie.hasNext())
{
3
Person p = (Person)ie.next();
4
p.doWork();
5
}
這種訪問(wèn)的特點(diǎn)是集合類(lèi)中的對(duì)象是同一類(lèi)對(duì)象Person,他們擁有功能的方法run,我們調(diào)用的恰好是這個(gè)共同的方法。
在大部份的情況下,這個(gè)是可以的,但在一些復(fù)雜的情況,如被訪問(wèn)者的繼承結(jié)構(gòu)復(fù)雜,被訪問(wèn)者的并不是同一類(lèi)對(duì)象,
也就是說(shuō)不是繼承自同一個(gè)根類(lèi)。方法名也并不相同。例如Java GUI中的事件就是一個(gè)例子。
例如這樣的問(wèn)題,有如下類(lèi)和方法:
類(lèi):PA ,方法:runPA();
類(lèi):PB ,方法:runPB();
類(lèi):PC ,方法:runPC();
類(lèi):PD ,方法:runPD();
類(lèi):PE ,方法:runPE();
有一個(gè)集合類(lèi)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());
....
二:解決
要求能訪問(wèn)到每個(gè)類(lèi)的對(duì)應(yīng)的方法。我們第一反應(yīng)應(yīng)該是這樣的。
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
}
三:新問(wèn)題及分析解決
當(dāng)數(shù)目變多的時(shí)候,維護(hù)if else是個(gè)費(fèi)力氣的事情:
仔細(xì)分析if,else做的工作,首先判斷類(lèi)型,然後根據(jù)類(lèi)型執(zhí)行相應(yīng)的函數(shù)
如何才能解決這兩個(gè)問(wèn)題呢?首先想到的是java的多態(tài),多態(tài)就是根據(jù)參數(shù)執(zhí)行相應(yīng)的內(nèi)容,
能很容易的解決第二個(gè)問(wèn)題,我們可以寫(xiě)這樣一個(gè)類(lèi):
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
}
這樣只要調(diào)用run方法,傳入對(duì)應(yīng)的參數(shù)就能執(zhí)行了。
還有一個(gè)問(wèn)題就是判斷類(lèi)型。由于重載(overloading)是靜態(tài)多分配(java語(yǔ)言本身是支持"靜態(tài)多分配"的。
關(guān)于這個(gè)概念請(qǐng)看這里)所以造成重載只根據(jù)傳入對(duì)象的定義類(lèi)型,而不是實(shí)際的類(lèi)型,所以必須在傳入前就確定類(lèi)型,
這可是個(gè)難的問(wèn)題,因?yàn)樵谌萜髦袑?duì)象全是Object,出來(lái)后要是判斷是什么類(lèi)型必須用
if (xx instanceof xxx)這種方法,如果用這種方法啟不是又回到了原點(diǎn),有沒(méi)有什么更好的辦法呢?
我們知到Java還有另外一個(gè)特點(diǎn),覆寫(xiě)(overriding),而覆寫(xiě)是"動(dòng)態(tài)單分配"的(關(guān)于這個(gè)概念見(jiàn)這里),
那如何利用這個(gè)來(lái)實(shí)現(xiàn)呢?看下邊這個(gè)方法:
我們讓上邊的一些類(lèi)PA PB PC PD PE都實(shí)現(xiàn)一個(gè)接口P,加入一個(gè)方法,accept();
1
public void accept(visitor v)
{
2
// 把自己傳入1
3
v.run( this );
4
}
5
然後在visitor中加入一個(gè)方法
6
public void run(P p)
{
7
// 把自己傳入2
8
p.accept( this );
9
}
10
// 這樣你在遍歷中可以這樣寫(xiě)
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
}
首先執(zhí)行的是"把自己傳入2",在這里由于Java的特性,實(shí)際執(zhí)行的是子類(lèi)的accept(),也就是實(shí)際類(lèi)的accept
然後是"把自己傳入1",在這里再次把this傳入,就明確類(lèi)型,ok我們巧妙的利用overriding解決了這個(gè)問(wèn)題
其實(shí)歸納一下第二部分,一個(gè)關(guān)鍵點(diǎn)是"自己認(rèn)識(shí)自己",是不是很可笑。
其實(shí)在計(jì)算計(jì)技術(shù)領(lǐng)域的很多技術(shù)上看起來(lái)很高深的東西,其實(shí)就是現(xiàn)有社會(huì)中人的生活方式的一種映射
而且這種方式是簡(jiǎn)單的不能再簡(jiǎn)單的方式。上邊的全部過(guò)程基本上是一個(gè)簡(jiǎn)單的visitor模式實(shí)現(xiàn),visitor模式
已經(jīng)是設(shè)計(jì)模式中比較復(fù)雜的模式了,但其實(shí)原理簡(jiǎn)單到你想笑。看看下邊這個(gè)比喻也許你的理解會(huì)更深刻。
四:一個(gè)幫助理解的比喻:
題目:指揮工人工作
條件:你有10個(gè)全能工人,10樣相同工作。
需求:做完工作
實(shí)現(xiàn):大喊一聲所有人去工作
條件變了,工人不是全能,但是工作相同,ok問(wèn)題不大
條件再變,工作不是相同,但工人是全能,ok問(wèn)題不大
以上三種情況在現(xiàn)實(shí)生活中是很少發(fā)生得,最多的情況是這樣:
10個(gè)工人,每人會(huì)做一種工作,10樣工作。你又一份名單Collection)寫(xiě)著誰(shuí)做什么。但你不認(rèn)識(shí)任何人
這個(gè)時(shí)候你怎么指揮呢,方案一:
你可以一個(gè)個(gè)的叫工人,然後問(wèn)他們名字,認(rèn)識(shí)他們,查名單,告訴他們做什么工作。
你可以直接叫出他們名字,告訴他們干什么,不需要知到他是誰(shuí)。
看起來(lái)很簡(jiǎn)單。但如果你要指揮10萬(wàn)人呢 ?而且人員是流動(dòng)的,每天的人不同,你每天拿到一張文檔。
其實(shí)很簡(jiǎn)單,最常用的做法是,你把這份名單貼在墻上,然後大喊一聲,所有人按照去看,按照自己的分配情況去做。
這里利用的關(guān)鍵點(diǎn)是"所有工人自己認(rèn)識(shí)自己",你不能苛求每個(gè)工人會(huì)做所有工作,不能苛求所有工作相同,但你
能要求所有工人都認(rèn)識(shí)自己。
再想想我們開(kāi)始的程序,每個(gè)工人對(duì)應(yīng)著PA PB PC PD PE....
所有的工人都使工人P
每個(gè)工人會(huì)做的東西不一樣runPA runPB runPC
你有一份名單Visitor(重載)記錄著誰(shuí)做什么工作。
看完上邊這些,你是不是會(huì)產(chǎn)生如下的問(wèn)題:
問(wèn)題:為什么不把這些方法的方法名做成一樣的,那就可以解決了。
例如,我們每個(gè)PA ,PB ,PC都加入一個(gè)run 方法,然後run內(nèi)部再調(diào)用自己對(duì)應(yīng)的runPx()方法。
答案:有些時(shí)候從不同的角度考慮,或者因?yàn)閷?shí)現(xiàn)的復(fù)雜度早成很難統(tǒng)一方法名。
例如上邊指揮人工作的例子的例子,其實(shí)run方法就是大叫一聲去工作,因?yàn)槊總€(gè)工人只會(huì)做一種工作,所以能行
但我們不能要求所有人只能會(huì)做一種事情,這個(gè)要求很愚蠢。所以如果每個(gè)工人會(huì)干兩種或者多種工作呢,
也就是我PA 有runPA() walkPA()等等方法, PB有runPB() climbPB()等等。。。
這個(gè)時(shí)候按照名單做事才是最好的辦法。
五:作者的話(huà)
所以說(shuō)模式中很多復(fù)雜的東西,在現(xiàn)實(shí)中其實(shí)是很基本的東西,多多代入代出能幫助理解模式。
看完本文,如果你對(duì)visitor模式有更多的興趣,想了解更多請(qǐng)看如下幾篇文章。
1,靜態(tài)分派,動(dòng)態(tài)分派,多分派,單分派 -------------- visitor模式準(zhǔn)備
2,訪問(wèn)差異類(lèi)型的集合類(lèi) ------------------------ visitor模式入門(mén)(本文)
3,visitor模式理論及學(xué)術(shù)概念------------------- visitor模式深入
4,重載overloading和覆寫(xiě)overriding哪個(gè)更早執(zhí)行-- visitor幫助篇
雖然排列順序是1,2,3,4 但是我個(gè)人建議的學(xué)習(xí)方式是2,1,3,4因?yàn)檫@個(gè)順序更方便一般人理解