阿里P7架構(gòu)師深入淺出Java的訪問者模式

一、引子

對于系統(tǒng)中一個已經(jīng)完成的類層次結(jié)構(gòu),我們已經(jīng)給它提供了滿足需求的接口。但是面對新增加的需求,我們應該怎么做呢?如果這是為數(shù)不多的幾次變動,而且你不用為了一個需求的調(diào)整而將整個類層次結(jié)構(gòu)統(tǒng)統(tǒng)地修改一遍,那么直接在原有類層次結(jié)構(gòu)上修改也許是個 不錯 的主意。

但是往往我們遇到的卻是:這樣的需求變動也許會不停的發(fā)生;更重要的是需求的任何變動可能都要讓你將整個類層次結(jié)構(gòu)修改個底朝天……。這種類似的操作分布在不同的類里面,不是一個好現(xiàn)象,我們要對這個結(jié)構(gòu)重構(gòu)一下了。

那么,訪問者模式也許是你很好的選擇。

二、定義與結(jié)構(gòu)

訪問者模式,顧名思義使用了這個模式后就可以在不修改已有程序結(jié)構(gòu)的前提下,通過添加額外的“訪問者”來完成對已有代碼功能的提升。

《設(shè)計模式》一書對于訪問者模式給出的定義為:表示一個作用于某對象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。從定義可以看出結(jié)構(gòu)對象是使用訪問者模式必須條件,而且這個結(jié)構(gòu)對象必須存在遍歷自身各個對象的方法。這便類似于java中的collection概念了。

以下是訪問者模式的組成結(jié)構(gòu)

  1. 訪問者角色(Visitor):為該對象結(jié)構(gòu)中具體元素角色聲明一個訪問操作接口。該操作接口的名字和參數(shù)標識了發(fā)送訪問請求給具體訪問者的具體元素角色。這樣訪問者就可以通過該元素角色的特定接口直接訪問它。
  2. 具體訪問者角色(Concrete Visitor):實現(xiàn)每個由訪問者角色(Visitor)聲明的操作。
  3. 元素角色(Element):定義一個Accept操作,它以一個訪問者為參數(shù)。
  4. 具體元素角色(Concrete Element):實現(xiàn)由元素角色提供的Accept操作。
  5. 對象結(jié)構(gòu)角色(Object Structure):這是使用訪問者模式必備的角色。它要具備以下特征:能枚舉它的元素;可以提供一個高層的接口以允許該訪問者訪問它的元素;可以是一個復合(組合模式)或是一個集合,如一個列表或一個無序集合。

來張類圖就能更加清晰的看清訪問者模式的結(jié)構(gòu)了。

那么像引言中假想的。我們應該做些什么才能讓訪問者模式跑起來呢?首先我們要在原有的類層次結(jié)構(gòu)中添加accept方法。然后將這個類層次中的類放到一個對象結(jié)構(gòu)中去。這樣再去創(chuàng)建訪問者角色……

三、舉例

本人閱歷實在可憐,沒能找到訪問者模式在實際應用中的例子。只好借《Thinking in Patterns with java》中的教學代碼一用。我稍微做了下修改。

import java.util.*;
import junit.framework.*;
//訪問者角色
interface Visitor {
     void visit(Gladiolus g);
     void visit(Runuculus r);
     void visit(Chrysanthemum c);
}
// The Flower hierarchy cannot be changed: 
//元素角色
interface Flower {
     void accept(Visitor v);
}
//以下三個具體元素角色
class Gladiolus implements Flower {
     public void accept(Visitor v) {
        v.visit(this);
    }
}
class Runuculus implements Flower {
     public void accept(Visitor v) {
        v.visit(this);
    }
}
class Chrysanthemum implements Flower {
     public void accept(Visitor v) {
        v.visit(this);
    }
}
// Add the ability to produce a string: 
//實現(xiàn)的具體訪問者角色
class StringVal implements Visitor {
     String s;
     public String toString() {
        return s;
    }
     public void visit(Gladiolus g) {
          s = "Gladiolus";
         
    }
     public void visit(Runuculus r) {
          s = "Runuculus";
         
    }
     public void visit(Chrysanthemum c) {
          s = "Chrysanthemum";
         
    }
}
// Add the ability to do "Bee" activities: 
//另一個具體訪問者角色
class Bee implements Visitor {
     public void visit(Gladiolus g) {
          System.out.println("Bee and Gladiolus");
         
    }
     public void visit(Runuculus r) {
          System.out.println("Bee and Runuculus");
         
    }
     public void visit(Chrysanthemum c) {
          System.out.println("Bee and Chrysanthemum");
         
    }
}
//這是一個對象生成器 
//這不是一個完整的對象結(jié)構(gòu),這里僅僅是模擬對象結(jié)構(gòu)中的元素
class FlowerGenerator {
     private static Random rand = new Random();
     public static Flower newFlower() {
           switch (rand.nextint(3)) {
              default: 
               case 0: return new Gladiolus();
              
               case 1: return new Runuculus();
               case 2: return new Chrysanthemum();
              
        }
         
    }
}
//客戶 測試程序
public class BeeAndFlowers extends TestCase {
     
    /* 
  在這里你能看到訪問者模式執(zhí)行的流程: 
  首先在客戶端先獲得一個具體的訪問者角色 
  遍歷對象結(jié)構(gòu) 
  對每一個元素調(diào)用accept方法,將具體訪問者角色傳入 
  這樣就完成了整個過程 
 */
     //對象結(jié)構(gòu)角色在這里才 組裝 上
     List flowers = new ArrayList();
     public BeeAndFlowers() {
          for (int i = 0; i < 10; i++) 
           flowers.add(FlowerGenerator.newFlower());
         
    }
     Visitor sval ;
     public void test() {
          // It’s almost as if I had a function to 
          // produce a Flower string representation: 
          //這個地方你可以修改以便使用另外一個具體訪問者角色
          sval = new StringVal();
          Iterator it = flowers.iterator();
          while(it.hasNext()) {
               ((Flower)it.next()).accept(sval);
               System.out.println(sval);
              
        }
         
    }
     public static void main(String args[]) {
          junit.textui.TestRunner.run(BeeAndFlowers.class);
         
    }
}

四、雙重分派

對了,你在上面的例子中體會到雙重分派的實現(xiàn)了沒有?

首先在客戶程序中將具體訪問者模式作為參數(shù)傳遞給具體元素角色(加亮的地方所示)。這便完成了一次分派。

進入具體元素角色后,具體元素角 色調(diào) 用作為參數(shù)的具體訪問者模式中的visitor方法,同時將自己(this)作為參數(shù)傳遞進去。具體訪問者模式再根據(jù)參數(shù)的不同來選擇方法來執(zhí)行(加亮的地方所示)。這便完成了第二次分派。

五、優(yōu)缺點及適用情況

先來看下訪問者模式的使用能否避免引言中的痛苦。使用了訪問者模式以后,對于原來的類層次增加新的操作,僅僅需要實現(xiàn)一個具體訪問者角色就可以了,而不必修改整個類層次。而且這樣符合“開閉原則”的要求。而且每個具體的訪問者角色都對應于一個相關(guān)操作,因此如果一個操作的需求有變,那么僅僅修改一個具體訪問者角色,而不用改動整個類層次。

看來訪問者模式確實能夠解決我們面臨的一些問題。

而且由于訪問者模式為我們的系統(tǒng)多提供了一層“訪問者”,因此我們可以在訪問者中添加一些對元素角色的額外操作。

但是“開閉原則”的遵循總是片面的。如果系統(tǒng)中的類層次發(fā)生了變化,會對訪問者模式產(chǎn)生什么樣的影響呢?你必須修改訪問者角色和每一個具體訪問者角色……

看來訪問者角色不適合具體元素角色經(jīng)常發(fā)生變化的情況。而且訪問者角色要執(zhí)行與元素角色相關(guān)的操作,就必須讓元素角色將自己內(nèi)部屬性暴露出來,而在java中就意味著其它的對象也可以訪問。這就破壞了元素角色的封裝性。而且在訪問者模式中,元素與訪問者之間能夠傳遞的信息有限,這往往也會限制訪問者模式的使用。

《設(shè)計模式》一書中給出了訪問者模式適用的情況

  1. 一個對象結(jié)構(gòu)包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴于其具體類的操作。
  2. 需要對一個對象結(jié)構(gòu)中的對象進行很多不同的并且不相關(guān)的操作,而你想避免讓這些操作“污染”這些對象的類。Visitor使得你可以將相關(guān)的操作集中起來定義在一個類中。
  3. 當該對象結(jié)構(gòu)被很多應用共享時,用Visitor模式讓每個應用僅包含需要用到的操作。
  4. 定義對象結(jié)構(gòu)的類很少改變,但經(jīng)常需要在此結(jié)構(gòu)上定義新的操作。改變對象結(jié)構(gòu)類需要重定義對所有訪問者的接口,這可能需要很大的代價。如果對象結(jié)構(gòu)類經(jīng)常改變,那么可能還是在這些類中定義這些操作較好。

你是否能很好的理解呢?

六、總結(jié)

這是一個巧妙而且復雜的模式,它的使用條件比較苛刻。當系統(tǒng)中存在著固定的數(shù)據(jù)結(jié)構(gòu)(比如上面的類層次),而有著不同的行為,那么訪問者模式也許是個不錯的選擇。

Java_蘇先生:專注于Java開發(fā)技術(shù)的研究與知識分享!
————END————

  • 點贊(感謝)
  • ...
  • 轉(zhuǎn)發(fā)(感謝)
  • ...
  • 關(guān)注(感謝)
  • ...
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 【學習難度:★★☆☆☆,使用頻率:★★★☆☆】直接出處:訪問者模式梳理和學習:https://github.com...
    BruceOuyang閱讀 1,026評論 2 2
  • 目錄 本文的結(jié)構(gòu)如下: 引言 什么是訪問者模式 模式的結(jié)構(gòu) 典型代碼 訪問者模式中的偽動態(tài)雙分派 代碼示例 訪問者...
    w1992wishes閱讀 944評論 0 6
  • 訪問者模式(Visitor) 在現(xiàn)實生活中,有些集合對象中存在多種不同的元素,且每種元素也存在多種不同的訪問者和處...
    Acton_zhang閱讀 612評論 0 1
  • 我是根師兄。 你的忙是越來越有意義嗎? 多年前我也很忙,但是到頭來一事無成?,F(xiàn)在回想都是瞎忙。 列入: 1.今天有...
    根師兄閱讀 253評論 0 0
  • 轉(zhuǎn)眼間,再有一個月就是農(nóng)歷的大年了。 自我感覺,近幾年的時間越來越不經(jīng)使用,還未曾感覺經(jīng)歷,就不知所蹤了! 不知道...
    四葉草_廣廣閱讀 384評論 12 10

友情鏈接更多精彩內(nèi)容