設(shè)計(jì)模式—訪問者(Visitor)模式

定義

訪問者模式是一種從操作的對(duì)象結(jié)構(gòu)中分離算法的方式。 它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用與這些元素的新操作揭鳞。它遵循開閉原則炕贵。

Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

visitor: n. 訪問者,參觀者野崇;視察者.

涉及角色

  • Visitor 抽象訪問者角色称开,為該對(duì)象結(jié)構(gòu)中具體元素角色聲明一個(gè)訪問操作接口。該操作接口的名字和參數(shù)標(biāo)識(shí)了發(fā)送訪問請(qǐng)求給具體訪問者的具體元素角色,這樣訪問者就可以通過該元素角色的特定接口直接訪問它鳖轰。
  • ConcreteVisitor.具體訪問者角色清酥,實(shí)現(xiàn)Visitor聲明的接口。
  • Element 定義一個(gè)接受訪問操作(accept())蕴侣,它以一個(gè)訪問者(Visitor)作為參數(shù)焰轻。
  • ConcreteElement 具體元素,實(shí)現(xiàn)了抽象元素(Element)所定義的接受操作接口昆雀。
  • ObjectStructure 結(jié)構(gòu)對(duì)象角色辱志,這是使用訪問者模式必備的角色。它具備以下特性:能枚舉它的元素忆肾;可以提供一個(gè)高層接口以允許訪問者訪問它的元素荸频;如有需要,可以設(shè)計(jì)成一個(gè)復(fù)合對(duì)象或者一個(gè)聚集(如一個(gè)列表或無序集合)

通俗理解

  • 我作為一個(gè)訪客(Visitor)到朋友家(Element)拜訪客冈,朋友之間喝喝酒旭从,聊聊天,再互相吹捧场仲。聊天的時(shí)候和悦,朋友告訴我他今年的表現(xiàn)(doSomthing),然后我就做(visit-self-method)一些對(duì)這件事的評(píng)價(jià)渠缕。
  • 老板作為視察者鸽素,查閱(訪問)手下員工的工作業(yè)績。老板是Visitor的抽象實(shí)現(xiàn)亦鳞,員工是Element的抽象實(shí)現(xiàn)馍忽。對(duì)象結(jié)構(gòu)(Object Structure)為員工的業(yè)績等信息
  • 家里有一臺(tái)電腦,電腦出現(xiàn)了一點(diǎn)問題燕差,那么我作為訪問者遭笋,想去了解電腦的那個(gè)部分出了問題。我Visitor徒探,電腦的各個(gè)部分(Element)瓦呼,查看有沒有壞(visit method)

應(yīng)該有很多類似的比喻,在開發(fā)的過程中多去思考测暗,做什么事情都要思考央串。

實(shí)現(xiàn)細(xì)節(jié)

  1. 定義一個(gè)表示Element的接口
  2. 實(shí)現(xiàn)Element接口。創(chuàng)建Element的實(shí)體類ConcreteElement
  3. 創(chuàng)建一個(gè)表示訪問者Visitor的接口
  4. 實(shí)現(xiàn)Visitor的接口碗啄,創(chuàng)建Visitor實(shí)體類ConcreteVisitor质和,(有時(shí)候會(huì)有多個(gè)訪問者)
  5. 使用Visitor實(shí)體類來訪問Element。

特性

優(yōu)點(diǎn)

  1. 符合單一職責(zé)原則
  2. 元素類可以通過接受不同的訪問者來實(shí)現(xiàn)對(duì)不同操作的擴(kuò)展挫掏。

缺點(diǎn)

  1. 具體元素對(duì)訪問者公布細(xì)節(jié)侦另,違背了迪米特法則。
  2. 違背了依賴倒置原則,訪問者依賴的是具體元素褒傅,而不是抽象元素弃锐。

適用場(chǎng)景

  1. 對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類很少改變,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作殿托。
  2. 需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作霹菊,而需要避免讓這些操作"污染"這些對(duì)象的類,也不希望在增加新操作時(shí)修改這些類支竹。

注意事項(xiàng):訪問者可以對(duì)功能進(jìn)行統(tǒng)一旋廷,可以做報(bào)表、UI礼搁、攔截器與過濾器饶碘。

案例

案例一
類圖

我們?nèi)z查汽車的各個(gè)部分是否能正常打印,使用Visitor根據(jù)不同的汽車部分來分發(fā)動(dòng)作馒吴。 而不是在汽車的各個(gè)部分來打印扎运。

interface CarElement {
    void accept(CarElementVisitor visitor);
}

interface CarElementVisitor {
    void visit(Body body);
    void visit(Car car);
    void visit(Engine engine);
    void visit(Wheel wheel);
}

class Car implements CarElement {
    CarElement[] elements;

    public Car() {
        this.elements = new CarElement[] {
            new Wheel("front left"), new Wheel("front right"),
            new Wheel("back left"), new Wheel("back right"),
            new Body(), new Engine()
        };
    }

    public void accept(final CarElementVisitor visitor) {
        for (CarElement elem : elements) {
            elem.accept(visitor);
        }
        visitor.visit(this);
    }
}

class Body implements CarElement {
    public void accept(final CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Engine implements CarElement {
    public void accept(final CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Wheel implements CarElement {
    private String name;

    public Wheel(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void accept(final CarElementVisitor visitor) {
        /*
         * accept(CarElementVisitor) in Wheel implements
         * accept(CarElementVisitor) in CarElement, so the call
         * to accept is bound at run time. This can be considered
         * the *first* dispatch. However, the decision to call
         * visit(Wheel) (as opposed to visit(Engine) etc.) can be
         * made during compile time since 'this' is known at compile
         * time to be a Wheel. Moreover, each implementation of
         * CarElementVisitor implements the visit(Wheel), which is
         * another decision that is made at run time. This can be
         * considered the *second* dispatch.
         */
        visitor.visit(this);
    }
}

class CarElementDoVisitor implements CarElementVisitor {
    public void visit(final Body body) {
        System.out.println("Moving my body");
    }

    public void visit(final Car car) {
        System.out.println("Starting my car");
    }

    public void visit(final Wheel wheel) {
        System.out.println("Kicking my " + wheel.getName() + " wheel");
    }

    public void visit(final Engine engine) {
        System.out.println("Starting my engine");
    }
}

class CarElementPrintVisitor implements CarElementVisitor {
    public void visit(final Body body) {
        System.out.println("Visiting body");
    }

    public void visit(final Car car) {
        System.out.println("Visiting car");
    }

    public void visit(final Engine engine) {
        System.out.println("Visiting engine");
    }

    public void visit(final Wheel wheel) {
        System.out.println("Visiting " + wheel.getName() + " wheel");
    }
}

public class VisitorDemo {
    public static void main(final String[] args) {
        final Car car = new Car();

        car.accept(new CarElementPrintVisitor());
        car.accept(new CarElementDoVisitor());
    }
}

/* 輸出內(nèi)容
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
*/
案例二
類圖

本質(zhì)上和案例一沒什么差別

// ComputerPart.java
public interface ComputerPart {
   public void accept(ComputerPartVisitor computerPartVisitor);
}

// Keyboard.java
public class Keyboard  implements ComputerPart {

   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

// Monitor.java
public class Monitor  implements ComputerPart {

   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

// Mouse.java
public class Mouse  implements ComputerPart {

   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

// Computer.java
public class Computer implements ComputerPart {
    
   ComputerPart[] parts;

   public Computer(){
      parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};        
   } 


   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      for (int i = 0; i < parts.length; i++) {
         parts[i].accept(computerPartVisitor);
      }
      computerPartVisitor.visit(this);
   }
}

// ComputerPartVisitor
public interface ComputerPartVisitor {
    public void visit(Computer computer);
    public void visit(Mouse mouse);
    public void visit(Keyboard keyboard);
    public void visit(Monitor monitor);
}

// ComputerPartDisplayVisitor.java
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {

   @Override
   public void visit(Computer computer) {
      System.out.println("Displaying Computer.");
   }

   @Override
   public void visit(Mouse mouse) {
      System.out.println("Displaying Mouse.");
   }

   @Override
   public void visit(Keyboard keyboard) {
      System.out.println("Displaying Keyboard.");
   }

   @Override
   public void visit(Monitor monitor) {
      System.out.println("Displaying Monitor.");
   }
}

// demo
public class VisitorPatternDemo {
   public static void main(String[] args) {

      ComputerPart computer = new Computer();
      computer.accept(new ComputerPartDisplayVisitor());
   }
}

/* 輸出
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.
*/

小結(jié)

主要記錄了在學(xué)習(xí)設(shè)計(jì)模式時(shí)的一些資料,對(duì)資料進(jìn)行了整理饮戳。想要深入理解設(shè)計(jì)模式豪治,還要多讀優(yōu)秀的代碼,在開發(fā)的時(shí)候多去思考相關(guān)的應(yīng)用場(chǎng)景扯罐。

附錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末负拟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子歹河,更是在濱河造成了極大的恐慌掩浙,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秸歧,死亡現(xiàn)場(chǎng)離奇詭異涣脚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)寥茫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矾麻,“玉大人纱耻,你說我怎么就攤上這事∠找” “怎么了弄喘?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長甩牺。 經(jīng)常有香客問我蘑志,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任急但,我火速辦了婚禮澎媒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘波桩。我一直安慰自己戒努,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布镐躲。 她就那樣靜靜地躺著储玫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪萤皂。 梳的紋絲不亂的頭發(fā)上撒穷,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音裆熙,去河邊找鬼端礼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弛车,可吹牛的內(nèi)容都是我干的齐媒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纷跛,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喻括!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贫奠,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤唬血,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后唤崭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拷恨,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年谢肾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腕侄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芦疏,死狀恐怖冕杠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酸茴,我是刑警寧澤分预,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站薪捍,受9級(jí)特大地震影響笼痹,放射性物質(zhì)發(fā)生泄漏配喳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一凳干、第九天 我趴在偏房一處隱蔽的房頂上張望晴裹。 院中可真熱鬧,春花似錦纺座、人聲如沸息拜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽少欺。三九已至,卻和暖如春馋贤,著一層夾襖步出監(jiān)牢的瞬間赞别,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工配乓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仿滔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓犹芹,卻偏偏與公主長得像崎页,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腰埂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容