love fish大鵬一曰同風(fēng)起,扶搖直上九萬里

          常用鏈接

          統(tǒng)計

          積分與排名

          friends

          link

          最新評論

          訪問差異類型的集合類--visitor模式入門(轉(zhuǎn))

          訪問差異類型的集合類--visitor模式入門
          本文對應(yīng)代碼下載這里
          一,問題提出
          訪問同一類型的集合類是我們最常見的事情了,我們工作中這樣的代碼太常見了。

          1 Iterator?ie? = ?list.iterator();
          2 while (ie.hasNext()) {
          3 ????Person?p? = ?(Person)ie.next();
          4 ????p.doWork();
          5 }


          這種訪問的特點(diǎn)是集合類中的對象是同一類對象Person,他們擁有功能的方法run,我們調(diào)用的恰好是這個共同的方法。
          在大部份的情況下,這個是可以的,但在一些復(fù)雜的情況,如被訪問者的繼承結(jié)構(gòu)復(fù)雜,被訪問者的并不是同一類對象,
          也就是說不是繼承自同一個根類。方法名也并不相同。例如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());
          ....
          二:解決
          要求能訪問到每個類的對應(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 }


          三:新問題及分析解決
          當(dāng)數(shù)目變多的時候,維護(hù)if else是個費(fèi)力氣的事情:
          仔細(xì)分析if,else做的工作,首先判斷類型,然後根據(jù)類型執(zhí)行相應(yīng)的函數(shù)
          如何才能解決這兩個問題呢?首先想到的是java的多態(tài),多態(tài)就是根據(jù)參數(shù)執(zhí)行相應(yīng)的內(nè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方法,傳入對應(yīng)的參數(shù)就能執(zhí)行了。
          還有一個問題就是判斷類型。由于重載(overloading)是靜態(tài)多分配(java語言本身是支持"靜態(tài)多分配"的。
          關(guān)于這個概念請看這里)所以造成重載只根據(jù)傳入對象的定義類型,而不是實(shí)際的類型,所以必須在傳入前就確定類型,
          這可是個難的問題,因?yàn)樵谌萜髦袑ο笕荗bject,出來后要是判斷是什么類型必須用
          if (xx instanceof xxx)這種方法,如果用這種方法啟不是又回到了原點(diǎn),有沒有什么更好的辦法呢?

          我們知到Java還有另外一個特點(diǎn),覆寫(overriding),而覆寫是"動態(tài)單分配"的(關(guān)于這個概念見這里),
          那如何利用這個來實(shí)現(xiàn)呢?看下邊這個方法:
          ?我們讓上邊的一些類PA PB PC PD PE都實(shí)現(xiàn)一個接口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 }


          首先執(zhí)行的是"把自己傳入2",在這里由于Java的特性,實(shí)際執(zhí)行的是子類的accept(),也就是實(shí)際類的accept
          然後是"把自己傳入1",在這里再次把this傳入,就明確類型,ok我們巧妙的利用overriding解決了這個問題
          其實(shí)歸納一下第二部分,一個關(guān)鍵點(diǎn)是"自己認(rèn)識自己",是不是很可笑。
          其實(shí)在計算計技術(shù)領(lǐng)域的很多技術(shù)上看起來很高深的東西,其實(shí)就是現(xiàn)有社會中人的生活方式的一種映射
          而且這種方式是簡單的不能再簡單的方式。上邊的全部過程基本上是一個簡單的visitor模式實(shí)現(xiàn),visitor模式
          已經(jīng)是設(shè)計模式中比較復(fù)雜的模式了,但其實(shí)原理簡單到你想笑。看看下邊這個比喻也許你的理解會更深刻。

          四:一個幫助理解的比喻:
          題目:指揮工人工作
          條件:你有10個全能工人,10樣相同工作。
          需求:做完工作
          實(shí)現(xiàn):大喊一聲所有人去工作

          條件變了,工人不是全能,但是工作相同,ok問題不大
          條件再變,工作不是相同,但工人是全能,ok問題不大

          以上三種情況在現(xiàn)實(shí)生活中是很少發(fā)生得,最多的情況是這樣:
          10個工人,每人會做一種工作,10樣工作。你又一份名單Collection)寫著誰做什么。但你不認(rèn)識任何人
          這個時候你怎么指揮呢,方案一:
          你可以一個個的叫工人,然後問他們名字,認(rèn)識他們,查名單,告訴他們做什么工作。
          你可以直接叫出他們名字,告訴他們干什么,不需要知到他是誰。
          看起來很簡單。但如果你要指揮10萬人呢 ?而且人員是流動的,每天的人不同,你每天拿到一張文檔。
          其實(shí)很簡單,最常用的做法是,你把這份名單貼在墻上,然後大喊一聲,所有人按照去看,按照自己的分配情況去做。
          這里利用的關(guān)鍵點(diǎn)是"所有工人自己認(rèn)識自己",你不能苛求每個工人會做所有工作,不能苛求所有工作相同,但你
          能要求所有工人都認(rèn)識自己。

          再想想我們開始的程序,每個工人對應(yīng)著PA PB PC PD PE....
          所有的工人都使工人P
          每個工人會做的東西不一樣runPA runPB runPC
          你有一份名單Visitor(重載)記錄著誰做什么工作。

          看完上邊這些,你是不是會產(chǎn)生如下的問題:
          問題:為什么不把這些方法的方法名做成一樣的,那就可以解決了。
          例如,我們每個PA ,PB ,PC都加入一個run 方法,然後run內(nèi)部再調(diào)用自己對應(yīng)的runPx()方法。
          答案:有些時候從不同的角度考慮,或者因?yàn)閷?shí)現(xiàn)的復(fù)雜度早成很難統(tǒng)一方法名。
          例如上邊指揮人工作的例子的例子,其實(shí)run方法就是大叫一聲去工作,因?yàn)槊總€工人只會做一種工作,所以能行
          但我們不能要求所有人只能會做一種事情,這個要求很愚蠢。所以如果每個工人會干兩種或者多種工作呢,
          也就是我PA 有runPA() walkPA()等等方法, PB有runPB() climbPB()等等。。。
          這個時候按照名單做事才是最好的辦法。

          五:作者的話
          所以說模式中很多復(fù)雜的東西,在現(xiàn)實(shí)中其實(shí)是很基本的東西,多多代入代出能幫助理解模式。

          看完本文,如果你對visitor模式有更多的興趣,想了解更多請看如下幾篇文章。
          1,靜態(tài)分派,動態(tài)分派,多分派,單分派 --------------?? visitor模式準(zhǔn)備
          2,訪問差異類型的集合類 ------------------------?? visitor模式入門(本文)
          3,visitor模式理論及學(xué)術(shù)概念-------------------?? visitor模式深入
          4,重載overloading和覆寫overriding哪個更早執(zhí)行--?? visitor幫助篇
          雖然排列順序是1,2,3,4 但是我個人建議的學(xué)習(xí)方式是2,1,3,4因?yàn)檫@個順序更方便一般人理解



          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-19 09:37 hannyu

          我有一個疑問,下面這段代碼是不是少寫了什么東西?我怎么覺得p.accept與v.run之間是死循環(huán)呢?請解釋一下
          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 } ??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-19 09:46 themax

          首先執(zhí)行的是"把自己傳入2",???
          應(yīng)該是"把自己傳入1"吧.

          ??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-19 21:53 dreamstone

          to hannyu :
          其實(shí)這就是visitor模式關(guān)鍵的地方,p.accept(v)的時候會發(fā)生向下轉(zhuǎn)型,所以執(zhí)行的是子類的accept()方法,而子類的accept()方法中,傳入this就是子類而不是父類了。也就是說PA中調(diào)用v.run(this)其實(shí)傳入的this是PA類型,而不是P類型了,這樣visitor重載的時候就會執(zhí)行run(Pa pa)而不是run(P p),所以不會死循環(huán)的。代碼在公司,明天把代碼上傳,你執(zhí)行一下就知道了。
          (上邊就是說的自己認(rèn)識自己了)??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-19 21:56 dreamstone

          to themax :
          可能我寫的有點(diǎn)歧意,我上邊的“把自己傳入2";是個label,相當(dāng)于姓名一,姓名二,下邊解釋的是拿它來當(dāng)label用,而不是把自己傳入給第二個,sorry。??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-19 22:31 dreamstone

          另外,加入了source的下載,見文章開始,雖然簡單,但是可以看看。
          個人感覺visitor模式是模式中比較復(fù)雜的。實(shí)現(xiàn)起來也是比較巧的.兩次this的利用。好像有一種學(xué)術(shù)的叫法"返還球"??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-20 09:22 hannyu

          多謝,看了源代碼終于明白了,visitor模式繞來繞去的真難理解。接口P應(yīng)該只起來一個輔助作用,如果有個特殊點(diǎn)的命名就容易理解一些了。比如visitee???回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-27 13:07 jounyc

          我覺得把Visitor中

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

          去掉也可以運(yùn)行,
          因?yàn)樵?p.accept(v); 時父類向下轉(zhuǎn)型,執(zhí)行子類的accept(Visitor v),
          然后就執(zhí)行子類在Visitor中對應(yīng)的run()了,和上面那段沒有關(guān)系阿。

          樓主出來解釋下。??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門 2006-12-27 19:52 dreamstone[匿名]

          呵呵,其實(shí)我當(dāng)時寫這個是為了這樣的情況,可能這種情況用的更多
          Visitor v = new Visitor();
          Iterator ie = list.iterator();
          while (ie.hasNext()) {
          P p = (P)ie.next();
          v.run(p);
          }
          這個時候那個函數(shù)就必須有了。因?yàn)閞un(p)的時候必須在編譯器有一個對應(yīng)的函數(shù),這兩個的區(qū)別是看想問題的角度了。
          是讓visitor 訪問每一個p
          還是讓每一個P運(yùn)行。
          不過更多情況應(yīng)該是v.run(p);更復(fù)合思考的習(xí)慣。后來發(fā)文的時候簡化了一下。


          ??回復(fù)??更多評論??

          # ?re: 訪問差異類型的集合類--visitor模式入門[未登錄]2007-02-09 11:52 xmlspy

          代碼中

          for(int i=0;i<list.size();i++){
          P p = (P)list.get(i);
          p.accept(v);
          }

          這種方式嚴(yán)重影響性能!!

          改成:

          P p =null;
          for(int i=0;i<list.size();i++){
          p = (P)list.get(i);
          p.accept(v);
          }
          ??回復(fù)??更多評論??

          posted on 2007-02-26 09:34 liaojiyong 閱讀(279) 評論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 昌邑市| 汕头市| 都江堰市| 德庆县| 望江县| 定襄县| 保靖县| 静乐县| 岱山县| 四川省| 平安县| 蒲城县| 南皮县| 塔河县| 鹿泉市| 二连浩特市| 安龙县| 潜江市| 凤庆县| 上蔡县| 龙岩市| 闻喜县| 泽普县| 炎陵县| 镇平县| 敖汉旗| 梁平县| 庆安县| 双柏县| 龙胜| 文登市| 鄂温| 林甸县| 炉霍县| 武冈市| 大竹县| 蓝田县| 秦皇岛市| 邹城市| 灵川县| 从化市|