Vincent.Chan‘s Blog

          常用鏈接

          統(tǒng)計(jì)

          積分與排名

          網(wǎng)站

          最新評(píng)論

          Iterator vs Visitor, Pull vs Push

          Iterator vs Visitor, Pull vs Push

          名詞界定

          Iterator Pattern 也叫做 Generator, Sequence, Stream 等。 Java 里面有 Iterator Interface ,大家應(yīng)該比較熟悉,不再贅述。

          ?

          完整的具有 Visitor Visited (Visitable) 兩個(gè)部分的 Visitor Pattern 的使用并不廣泛。

          簡(jiǎn)單的只有 Visitor 部分的 Simple Visitor Pattern 比較常見(jiàn),比如 Callback, Interceptor, Filter , Functor, Selector, Extractor 等,都可以看作是 Simple Visitor Pattern 。

          它們都只有 Visitor 部分,而沒(méi)有 Visited 部分?;蛘哒f(shuō)他們的 Visitor 需要處理的 Visited Object 通常只有一種通用數(shù)據(jù)類型,所以不需要專門(mén)提出來(lái)一個(gè) Visited Interface -- accept(visitor) 。

          ?

          這種情況和 Observer Pattern 很相似。不過(guò)這也不奇怪,很多 Design Pattern 都是非常類似的,有的幾乎只有名字不同。

          為了避免不必要的口舌之爭(zhēng)后,這么說(shuō)吧, Visitor Observer 的側(cè)重點(diǎn)不同。

          Visitor 一般來(lái)說(shuō)是 Visit 一個(gè)集合,通常在一個(gè)遍歷算法中密集完成,獲取的信息( Node Object )之間一般都有密切關(guān)聯(lián),比如父子關(guān)系,兄弟關(guān)系。

          Observer Patten 則是監(jiān)聽(tīng)一個(gè)長(zhǎng)期運(yùn)行的系統(tǒng),零零散散地不定期地運(yùn)行,獲取的信息之間不存在密切聯(lián)系,或者說(shuō),沒(méi)什么關(guān)系。比如訂閱的報(bào)紙到了,訂購(gòu)的牛奶到了。

          ?

          費(fèi)了半天口舌,澄清各種可能的誤會(huì),我們繼續(xù) Iterator vs Visitor 之旅。

          Iterator Pattern Simple Visitor Pattern 處理的問(wèn)題領(lǐng)域幾乎是重合的。它們面對(duì)的共同問(wèn)題模型的組成部分如下:

          (1) 有一個(gè)算法 Algorithm ,通常是遍歷算法( Traversal )。而且通常是復(fù)雜的數(shù)據(jù)結(jié)構(gòu), Tree 、 Graph 等結(jié)構(gòu)的遍歷算法。

          (2) 算法的使用者,需要和算法的每一步遇到的 Node Object 進(jìn)行交流。

          ?

          所不同的是,

          Iterator 是一種主動(dòng)模型, Pull 模型, Ask and Get Iterator 聽(tīng)候用戶的調(diào)遣。

          Vistor 是一種被動(dòng)模型, Push 模型, Plugin / callback 模型, Push and Pray and Wait 。 Visitor 聽(tīng)候算法的調(diào)遣。

          ?

          Iterator 相當(dāng)于算法公司的一名業(yè)務(wù)人員,代表公司和用戶打交道。用戶并不需要深入到算法公司內(nèi)部。

          Visitor 相當(dāng)于用戶派出的代表,深入到算法公司內(nèi)部,由算法公司安排訪問(wèn)行程。

          ?

          Iterator 的典型使用方法是:

          iterator = traversal.getIterator();

          item = iterator.next();

          do some thing with item

          ?

          Vistor 的典型使用方法是

          visitor = new Visitor(){

          ? .. visit(item) { do something with item }

          };

          traversal.traverse(visitor);

          ?

          一個(gè)典型的例子是 StAX SAX 和兩種操作 XML 的方式。

          (參見(jiàn) http://www.xmlpull.org/ ,關(guān)于 StAX 的典型用法,網(wǎng)上有些經(jīng)典文章,本文不再贅述。請(qǐng)使用 StAX XMLPull XML 等關(guān)鍵字進(jìn)行搜索)

          StAX 就是一個(gè)典型的 Pull Model, Iterator Pattern 。

          SAX Push Model, Simple Visitor Pattern (Event Listener) 。(如果較真,你當(dāng)然可以把它叫做 Oberver Pattern )。

          ?

          另外一個(gè)有趣的例子是 DOM Traversal 。

          W3 DOM Level 2 規(guī)范定義了 DOM Traversal

          http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/traversal.html

          DocumentTraversal, TreeWalker, NodeIterator, NodeFilter

          ?

          DOM Traversal 主要是一個(gè) Iterator Pattern TreeWalker, NodeIterator 都是 Iterator ;但 DOM Traversal 同時(shí)也是一個(gè)簡(jiǎn)單的 Visitor Pattern —— NodeFiter 可以作為簡(jiǎn)單的 Visitor 被注入到 Traversal 算法里面,對(duì)遇到的每個(gè) Node 進(jìn)行過(guò)濾。

          不過(guò),這個(gè) NodeFiter method name 比較有意思,叫做 accept 。而我們知道 Visitor Pattern Visited Object 具有一個(gè) accept 方法。不過(guò),不要誤會(huì)。這個(gè) NodeFilter 仍然是一個(gè) Visitor 。這里的 Accept 的意思就是 Intercept, Filter 。

          用法是這樣。

          NodeIterator iterator = DocumentTraversal.createNodeIterator(…NodeFilter …)

          ?

          按照我的設(shè)想,這個(gè) API 設(shè)計(jì),還可以有另外的思路。把 Filter 加在 Iterator 身上,而不是加在 Traversal 算法身上。因?yàn)?/span> Iterator Pattern 很容易地做到這一點(diǎn)。

          NodeIterator iterator = New FilteredIterator(

          ?? DocumentTraversal().createNodeIterator() , … NodeFilter …);

          ?

          這樣, DOM Traversal 就是一個(gè)純粹的 Iterater Pattern 了。

          特性比較

          Iterator 屬于問(wèn)答模式,或者說(shuō)消費(fèi)者 / 生產(chǎn)者模式。

          如果消費(fèi)者不問(wèn)不要,生產(chǎn)者就不答不給。 Iterator 的使用者完全掌握了主動(dòng)權(quán),是控制舞步節(jié)奏的領(lǐng)舞者,隨時(shí)可以中止這場(chǎng)問(wèn)答游戲。

          Iterator 的用法本身就是 Lazy 的,一問(wèn)一答,遍歷算法停在那里恭候 Iterator 使用者的調(diào)遣。

          ?

          Visitor 則完全是被動(dòng)的, Visitor 的提供者 / 使用者把 Visitor 扔到 Traversal 算法里面,然后運(yùn)行算法,同時(shí)祈禱并等候算法的完成( Push and Wait ),完全失去了控制權(quán),只能等待算法整個(gè)完成或者中止,才能重新拿到控制權(quán)。

          Vistor 的用法很難做到 Lazy ,算法必須提供一些機(jī)制,接受 Visitor 每一步調(diào)用發(fā)出的指令,進(jìn)行相應(yīng)的策略選擇。

          換句話說(shuō), Visitor Pattern 里面的算法必須做出相應(yīng)的 Lazy 支持,而且 Visitor 必需積累前面步驟的狀態(tài),然后判斷這次調(diào)用中發(fā)出什么樣的指令。

          ?

          比如,有這樣一個(gè)需求:遍歷一棵樹(shù),搜集到前 5 個(gè)名字是 Apple Node 。然后返回這 5 個(gè) Node 。假設(shè)樹(shù)遍歷算法已經(jīng)有了。

          這時(shí)候采用 Iterator Pattern 視線起來(lái)很容易。不再多說(shuō)。

          Visitor Pattern 則需要:

          Vistor 使用一個(gè)集合來(lái)保存每次遇到的名字是 Apple Node ,每次都判斷是否已經(jīng)找到了 5 個(gè),如果已經(jīng)找到,那么發(fā)出一個(gè) Stop Signal 給算法。

          如果遍歷算法不接受這種指令怎么辦?

          只好等待算法完成。或者實(shí)在等不及,預(yù)計(jì)到后面還有上萬(wàn)個(gè) Node 需要遍歷,那么就干脆

          throw new RuntimeException(),? throw new Throwable()

          也許算法并沒(méi)有聰明到捕獲這些 Exception 。那么這個(gè) Trick 就成功了。外面使用一個(gè) Try Catch 捕獲這個(gè) Exception 。不過(guò),這是 Very Very Bad Smell

          ?

          Iterator 的應(yīng)用場(chǎng)景是這樣:

          我 在商品定購(gòu)目錄上看到一個(gè)公司有我感興趣的產(chǎn)品系列。于是我打電話給該公司,要求派一個(gè)銷售代表來(lái)。銷售代表上門(mén)之后,從包里拿出一個(gè)一個(gè)的產(chǎn)品給你看, 我看了幾個(gè),沒(méi)什么滿意的,于是打了個(gè)哈欠說(shuō),今天就先到這里吧,下次再說(shuō)。打發(fā)了銷售代表,我就轉(zhuǎn)身去做自己的事情了。

          我的地盤(pán),我做主。這就是 Iterator Pattern 的理念。

          ?

          Vistior 的應(yīng)用場(chǎng)景是這樣:

          我 在商品定購(gòu)目錄上看到一個(gè)公司有我感興趣的產(chǎn)品系列。于是我上門(mén)拜訪該公司,公司給我安排了一場(chǎng)產(chǎn)品性能展示,我看了幾個(gè)之后,沒(méi)有什么滿意的,于是我 說(shuō),我肚子疼,想先回去了。遇到好心的公司代表,當(dāng)然說(shuō),身體要緊,慢走。遇到固執(zhí)的公司代表,一定會(huì)說(shuō),對(duì)不起,我們公司有自己的工作流程,完成產(chǎn)品演 示之前,產(chǎn)品廳的門(mén)鎖是打不開(kāi)的。我只好勃然大怒,吵吵嚷嚷( throw exception ),期待能夠殺出重圍,這時(shí)候,假設(shè)該公司的保安系統(tǒng)反映比較靈敏( try catch every visitor exception ),就會(huì)有幾個(gè)保安跑過(guò)來(lái),把我按在椅子上繼續(xù)聽(tīng)講。

          入鄉(xiāng)隨俗,客隨主便。別人的地盤(pán),別人做主。這就是 Visitor Pattern 的理念。

          ?

          Iterator Pattern 的優(yōu)勢(shì)當(dāng)然不僅如此。這只是個(gè)特殊的例子。

          更常見(jiàn)的是, Iterator Pattern 能夠支持基于 Iterator 的很多算法。

          比如, Functional Programming Map, Reduce, Filter 等函數(shù)都是接受一個(gè) Iterator (Sequence, List, Stream ) 。 Map, Filter 等函數(shù)還可以組合成一個(gè)新的 Iterator 。這個(gè)組合可以一直下去。

          當(dāng)然, Visitor 也是可以組合的。但是限制嚴(yán)格,缺乏擴(kuò)展性。

          ?

          比如這樣一個(gè)需求:

          T1 T2 兩棵樹(shù)。首先遍歷 T1 10 個(gè) Node ,如果發(fā)現(xiàn) Apple ,那么摘下來(lái),然后繼續(xù)遍歷,如果 10 步都沒(méi)有發(fā)現(xiàn) Apple ,那么切換到 T2 ;遍歷 T2 的規(guī)則也是如此, 10 步之內(nèi)發(fā)現(xiàn)目標(biāo),就繼續(xù),否則就切換到 T1

          Iterator Pattern 實(shí)現(xiàn)起來(lái)很簡(jiǎn)單。相當(dāng)于我是買(mǎi)方,情勢(shì)是買(mǎi)方市場(chǎng),我可以讓兩個(gè)公司的銷售代表同時(shí)到我的公司來(lái),我可以同時(shí)接待他們,讓他們各自按順序展示自己的產(chǎn)品。

          Visitor Pattern 怎么做?情勢(shì)是賣(mài)方市場(chǎng),我巴巴地跑上門(mén)去,看 T1 公司的產(chǎn)品展示,看了 10 個(gè)之后說(shuō),請(qǐng)送我到 T2 公司的產(chǎn)品展示現(xiàn)場(chǎng),我看 10 個(gè)之后,再回來(lái)。

          ?

          一個(gè)用戶可以同時(shí)使用多個(gè)算法的 Iterator ;但是用戶的一個(gè) Visitor 只能同時(shí)進(jìn)入一個(gè)算法。

          這就是兩者核心理念的不同。

          實(shí)現(xiàn)難度

          讀者說(shuō)了, Iterator 這么方便,你就使用 Iterator 好了,說(shuō)這么多干什么。

          如果別人提供了 Iterator ,我當(dāng)然會(huì)使用。

          ?

          現(xiàn)在的問(wèn)題是,假設(shè)你是算法公司的成員。你是提供 Visitor Pattern API ,還是 Iterator Pattern API ?

          Visitor Pattern 的實(shí)現(xiàn)比較簡(jiǎn)單。自己知道自己公司的內(nèi)部組織結(jié)構(gòu),一個(gè)一個(gè)的遍歷,并傳遞給 Visitor 就行了。

          Iterator Pattern 的實(shí)現(xiàn)難度,可以說(shuō),那是相當(dāng)?shù)拇蟆?/span>

          內(nèi)部數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單的數(shù)組、鏈表好說(shuō),做一個(gè)類似于 Closure, Context, Continuation 的保存了當(dāng)前調(diào)用步驟(數(shù)組索引,或者當(dāng)前指針)和調(diào)用環(huán)境(內(nèi)部數(shù)據(jù)集)的結(jié)構(gòu),返回給用戶就可以了。用戶每次調(diào)用 iterator.next , iterator 就把索引或指針向后移動(dòng)一下。

          如果是內(nèi)部數(shù)據(jù)復(fù)雜的 Tree, Graph 結(jié)構(gòu),就相當(dāng)復(fù)雜了。比如是遍歷一棵樹(shù),而且這棵樹(shù)的 Node 里面沒(méi)有 Parent 引用,那么 Iterator 必須自己維護(hù)一個(gè)棧把前面的所有的 Parent Node 都保存起來(lái)。當(dāng)用戶調(diào)用 iterator.next 的時(shí)候, iterator 就必須檢查自己當(dāng)前的狀態(tài),如果所有的 Child Node 都走完了,那么就要返回到上面的 Parent ,繼續(xù)檢查。

          而在 Visitor Pattern 里面,這個(gè)算法的實(shí)現(xiàn)簡(jiǎn)直是小菜一碟,只要一個(gè)簡(jiǎn)單的遞歸就夠了。計(jì)算機(jī)會(huì)自動(dòng)幫你分配和管理運(yùn)行棧,保存前面的 Parent Node ,執(zhí)行返回的時(shí)候,這個(gè) Parent Node 又自動(dòng)交還給你。

          Coroutine

          有沒(méi)有簡(jiǎn)單的方法來(lái)實(shí)現(xiàn) Iterator Pattern API 呢?如同實(shí)現(xiàn) Visitor Pattern API 那樣容易?

          幸福得象花兒一樣。簡(jiǎn)單得像 Visitor 一樣。能不能那樣?

          ?

          聰明的人們把目光轉(zhuǎn)向了 Coroutine 。

          Coroutine 本來(lái)是一個(gè)通用的概念。表示幾個(gè)協(xié)同工作的程序。

          比如,消費(fèi)者 / 生產(chǎn)者,你走幾步,我走幾步;下棋對(duì)弈,你一步我一步。

          由于協(xié)同工作的程序通常只有 2 個(gè),而且這兩個(gè)程序交換的數(shù)據(jù)通常只有一個(gè)。于是人們就很容易想到用 Coroutine 來(lái)實(shí)現(xiàn) Iterator

          這里面 Iterator 就是 Coroutine 里面的生產(chǎn)者 Producer 角色,數(shù)據(jù)提供者。所以,也叫做 Generator

          每次 Iterator 程序就是等在那里,一旦用戶(消費(fèi)者 Consumer 角色)調(diào)用了 iterator.next, Iterator 就繼續(xù)向下執(zhí)行一步,然后把當(dāng)前遇到的內(nèi)部數(shù)據(jù)的 Node 放到一個(gè)消費(fèi)者用戶能夠看到的公用的緩沖區(qū)(比如,直接放到消費(fèi)者線程棧里面的局部變量)里面,然后自己就停下來(lái)( wait )。然后消費(fèi)者用戶就從緩沖區(qū)里面獲得了那個(gè) Node

          這樣 Iterator 就可以自顧自地進(jìn)行遞歸運(yùn)算,不需要自己管理一個(gè)棧,而是迫使計(jì)算機(jī)幫助它分配和管理運(yùn)行棧。于是就實(shí)現(xiàn)了幸福得像花兒一樣,簡(jiǎn)單得像 Visitor 一樣的夢(mèng)想。

          ?

          比如,這樣一段代碼,遍歷一棵二叉樹(shù)。

          public class TreeWalker : Coroutine {
          ??? private TreeNode _tree;
          ??? public TreeWalker(TreeNode tree) { _tree?= tree; }
          ??? protected override Execute() {
          ??????? Walk(_tree);
          ??? }
          ??? private void Walk(TreeNode tree) {
          ??????? if (tree != null) {
          ??????????? Walk(tree.Left);
          ??????????? Yield(tree);
          ??????????? Walk(tree.Right);
          ??????? }
          ??? }
          }

          ?

          其中的 Yield 指令是關(guān)鍵。意思是,首先把當(dāng)前 Node 甩到用戶的數(shù)據(jù)空間,然后自己暫停運(yùn)行(類似于 java thread yield 方法或者 object.wait 方法),把自己當(dāng)前運(yùn)行的線程掛起來(lái),這樣虛擬機(jī)就為自己保存了當(dāng)前的運(yùn)行棧( context )。

          有人說(shuō),這不就是 continuation 嗎?

          對(duì)。只是 Coroutine 這里多了一個(gè)產(chǎn)生并傳遞數(shù)據(jù)的動(dòng)作。

          實(shí)現(xiàn)原理如果用 Java Thread 表示大概就是這樣。當(dāng)然下面的代碼只是一個(gè)示意。網(wǎng)上有具體的 Java Coroutine 實(shí)現(xiàn),具體代碼我也沒(méi)有看過(guò),想來(lái)實(shí)現(xiàn)原理大致如此。

          ?

          public class TreeIterator implements Iterator{

          ?? TreeWalker walker;

          ??? // start the walker thread ..

          ?? Object next(){

          ???? walker.notify();

          ???? // wait for a while so that walker can continue run

          ???? return walker.currentNode;

          ?? }

          }

          ?

          class TreeWalker implements Runnable{

          ??? TreeNode currentNode;
          ???

          TreeWarker(root){?

          ? ??currentNode = root;

          }

          void run(){

          ? walk(curentNode);

          }

          ?

          private void Walk(TreeNode tree) {
          ??????? if (tree != null) {
          ??????????? Walk(tree.Left);

          currentNode = tree;

          this.wait();

          Walk(tree.Right);
          ??????? }
          ??? }
          }

          ?

          我們看到, Iterator 本身是一個(gè) Thread ,用戶也是一個(gè) Thread 。 Iterator Thread 把數(shù)據(jù)傳遞給 User Thread 。

          說(shuō)實(shí)話,我寧可自己維護(hù)一個(gè) Stack ,也不愿意引入 Coroutine 這類 Thread Control 的方式來(lái)實(shí)現(xiàn) Iterator 。

          總結(jié)

          千言萬(wàn)語(yǔ)一句話。

          Iterator 是好的,但不是免費(fèi)的。

          posted on 2006-07-16 01:47 Vincent.Chen 閱讀(388) 評(píng)論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 香港 | 监利县| 新蔡县| 石林| 尼玛县| 奈曼旗| 巴南区| 冀州市| 大田县| 巴东县| 师宗县| 米脂县| 裕民县| 盘锦市| 光山县| 大足县| 福鼎市| 惠来县| 邵武市| 通榆县| 新巴尔虎右旗| 商河县| 广平县| 江安县| 海宁市| 邢台县| 武鸣县| 东光县| 古丈县| 峨山| 乌拉特后旗| 交口县| 营山县| 磐石市| 钟祥市| 资阳市| 清镇市| 裕民县| 柞水县| 延安市| 武强县|