設(shè)計(jì)模式系列 — 訪問(wèn)者模式

image

點(diǎn)贊再看缀磕,養(yǎng)成習(xí)慣,公眾號(hào)搜一搜【一角錢(qián)技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章覆积。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章浅蚪。

前言

23種設(shè)計(jì)模式快速記憶的請(qǐng)看上面第一篇皮胡,本篇和大家一起來(lái)學(xué)習(xí)訪問(wèn)者模式相關(guān)內(nèi)容。

image

這是23種設(shè)計(jì)模式的最后一個(gè)——訪問(wèn)者模式,這個(gè)模式確實(shí)不怎么好理解洪己,不怎么好用妥凳,而且實(shí)際中也很少用到這個(gè)設(shè)計(jì)模式。

在現(xiàn)實(shí)生活中答捕,有些集合對(duì)象中存在多種不同的元素绞愚,且每種元素也存在多種不同的訪問(wèn)者和處理方式。例如送悔,公園中存在多個(gè)景點(diǎn)玉转,也存在多個(gè)游客,不同的游客對(duì)同一個(gè)景點(diǎn)的評(píng)價(jià)可能不同沃琅;醫(yī)院醫(yī)生開(kāi)的處方單中包含多種藥元素哗咆,査看它的劃價(jià)員和藥房工作人員對(duì)它的處理方式也不同,劃價(jià)員根據(jù)處方單上面的藥品名和數(shù)量進(jìn)行劃價(jià)益眉,藥房工作人員根據(jù)處方單的內(nèi)容進(jìn)行抓藥晌柬。

這些被處理的數(shù)據(jù)元素相對(duì)穩(wěn)定而訪問(wèn)方式多種多樣的數(shù)據(jù)結(jié)構(gòu),如果用“訪問(wèn)者模式”來(lái)處理比較方便郭脂。訪問(wèn)者模式能把處理方法從數(shù)據(jù)結(jié)構(gòu)中分離出來(lái)年碘,并可以根據(jù)需要增加新的處理方法,且不用修改原來(lái)的程序代碼與數(shù)據(jù)結(jié)構(gòu)朱庆,這提高了程序的擴(kuò)展性和靈活性盛泡。

模式定義

將作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作分離出來(lái)封裝成獨(dú)立的類(lèi),使其在不改變數(shù)據(jù)結(jié)構(gòu)的前提下可以添加作用于這些元素的新的操作娱颊,為數(shù)據(jù)結(jié)構(gòu)中的每個(gè)元素提供多種訪問(wèn)方式傲诵。它將對(duì)數(shù)據(jù)的操作與數(shù)據(jù)結(jié)構(gòu)進(jìn)行分離,是行為類(lèi)模式中最復(fù)雜的一種模式箱硕。

訪問(wèn)者(Visitor)模式實(shí)現(xiàn)的關(guān)鍵是如何將作用于元素的操作分離出來(lái)封裝成獨(dú)立的類(lèi)

image

模板實(shí)現(xiàn)如下

package com.niuh.designpattern.visitor.v1;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * <p>
 * 訪問(wèn)者模式
 * </p>
 */
public class VisitorPattern {
    public static void main(String[] args) {
        ObjectStructure os = new ObjectStructure();
        os.add(new ConcreteElementA());
        os.add(new ConcreteElementB());
        Visitor visitor = new ConcreteVisitorA();
        os.accept(visitor);
        System.out.println("=================");
        visitor = new ConcreteVisitorB();
        os.accept(visitor);
    }
}

//抽象訪問(wèn)者
interface Visitor {
    void visit(ConcreteElementA element);

    void visit(ConcreteElementB element);
}

//具體訪問(wèn)者A類(lèi)
class ConcreteVisitorA implements Visitor {
    public void visit(ConcreteElementA element) {
        System.out.println("具體訪問(wèn)者A訪問(wèn)-->" + element.operationA());
    }

    public void visit(ConcreteElementB element) {
        System.out.println("具體訪問(wèn)者A訪問(wèn)-->" + element.operationB());
    }
}

//具體訪問(wèn)者B類(lèi)
class ConcreteVisitorB implements Visitor {
    public void visit(ConcreteElementA element) {
        System.out.println("具體訪問(wèn)者B訪問(wèn)-->" + element.operationA());
    }

    public void visit(ConcreteElementB element) {
        System.out.println("具體訪問(wèn)者B訪問(wèn)-->" + element.operationB());
    }
}

//抽象元素類(lèi)
interface Element {
    void accept(Visitor visitor);
}

//具體元素A類(lèi)
class ConcreteElementA implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operationA() {
        return "具體元素A的操作拴竹。";
    }
}

//具體元素B類(lèi)
class ConcreteElementB implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operationB() {
        return "具體元素B的操作。";
    }
}

//對(duì)象結(jié)構(gòu)角色
class ObjectStructure {
    private List<Element> list = new ArrayList<Element>();

    public void accept(Visitor visitor) {
        Iterator<Element> i = list.iterator();
        while (i.hasNext()) {
            ((Element) i.next()).accept(visitor);
        }
    }

    public void add(Element element) {
        list.add(element);
    }

    public void remove(Element element) {
        list.remove(element);
    }
}

輸出結(jié)果如下

具體訪問(wèn)者A訪問(wèn)-->具體元素A的操作剧罩。
具體訪問(wèn)者A訪問(wèn)-->具體元素B的操作栓拜。
=================
具體訪問(wèn)者B訪問(wèn)-->具體元素A的操作。
具體訪問(wèn)者B訪問(wèn)-->具體元素B的操作惠昔。

解決的問(wèn)題

穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問(wèn)題幕与。

需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類(lèi)镇防,使用訪問(wèn)者模式將這些封裝到類(lèi)中啦鸣。

模式組成

組成(角色) 作用
抽象訪問(wèn)者(Visitor)角色 定義一個(gè)訪問(wèn)具體元素的接口,為每個(gè)具體元素類(lèi)對(duì)應(yīng)一個(gè)訪問(wèn)操作 visit() 来氧,該操作中的參數(shù)類(lèi)型標(biāo)識(shí)了被訪問(wèn)的具體元素诫给。
具體訪問(wèn)者(ConcreteVisitor)角色 實(shí)現(xiàn)抽象訪問(wèn)者角色中聲明的各個(gè)訪問(wèn)操作香拉,確定訪問(wèn)者訪問(wèn)一個(gè)元素時(shí)該做什么。
抽象元素(Element)角色 聲明一個(gè)包含接受操作 accept() 的接口中狂,被接受的訪問(wèn)者對(duì)象作為 accept() 方法的參數(shù)凫碌。
具體元素(ConcreteElement)角色 實(shí)現(xiàn)抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visit(this) 胃榕,另外具體元素中可能還包含本身業(yè)務(wù)邏輯的相關(guān)操作盛险。
對(duì)象結(jié)構(gòu)(Object Structure)角色 是一個(gè)包含元素角色的容器,提供讓訪問(wèn)者對(duì)象遍歷容器中的所有元素的方法勋又,通常由 List枉层、Set、Map 等聚合類(lèi)實(shí)現(xiàn)赐写。

實(shí)例說(shuō)明

實(shí)例概況

用訪問(wèn)者模式實(shí)現(xiàn)一個(gè)用戶訪問(wèn)博客的場(chǎng)景

分析:用戶可以通過(guò)電腦上的Web方式(訪問(wèn)者)或者手機(jī)APP方式(訪問(wèn)者)去訪問(wèn)博客,每篇博客是一個(gè)元素膜赃,然后博客列表是一個(gè)對(duì)象結(jié)構(gòu)類(lèi)挺邀。

使用步驟

步驟1:定義抽象訪問(wèn)者(Visitor)

abstract class Visitor {
    public abstract void visitBlog(Element element);
}

步驟2:定義具體訪問(wèn)者(ConcreteVisitor),web 和 app兩種

//具體訪問(wèn)者(ConcreteVisitor)
class WebVisitor extends Visitor {
    public void visitBlog(Element element) {
        System.out.println("通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:" + element.blogName);
    }
}

//具體訪問(wèn)者(ConcreteVisitor)
class AppVisitor extends Visitor {
    public void visitBlog(Element element) {
        System.out.println("通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:" + element.blogName);
    }
}

步驟3:定義抽象元素(Element)

//抽象元素(Element)
abstract class Element {
    public String blogName;

    abstract public void accept(Visitor visotr);
}

步驟4:定義具體元素(ConcreteElement)跳座,即博客

//具體元素(ConcreteElement)
class BlogElement extends Element {
    public BlogElement(String blogname) {
        this.blogName = blogname;
    }

    public void accept(Visitor visitor) {
        visitor.visitBlog(this);
    }
}

步驟5:定義對(duì)象結(jié)構(gòu)類(lèi)(ObjectStructure)端铛,即博客列表

//對(duì)象結(jié)構(gòu)類(lèi)(ObjectStructure)
class Blogs {
    private List<Element> blogList = new ArrayList<Element>();

    public void addBlog(Element element) {
        blogList.add(element);
    }

    public void removeBlog(Element element) {
        blogList.remove(element);
    }

    public void accept(Visitor visitor) {
        Iterator<Element> i = blogList.iterator();
        while (i.hasNext()) {
            ((Element) i.next()).accept(visitor);
        }
    }

}

步驟6:測(cè)試驗(yàn)證

public class VisitorPattern {
    public static void main(String[] args) {
        Blogs blogs = new Blogs();
        blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第一篇博文"));
        blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第二篇博文"));
        blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第三篇博文"));
        blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第四篇博文"));

        Visitor webVisit = new WebVisitor();
        Visitor appVisit = new AppVisitor();

        blogs.accept(webVisit);
        blogs.accept(appVisit);

    }
}

輸出結(jié)果

通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第一篇博文
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第二篇博文
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第三篇博文
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第四篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第一篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第二篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第三篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第四篇博文

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

訪問(wèn)者(Visitor)模式是一種對(duì)象行為型模式,其主要優(yōu)點(diǎn)如下疲眷。

  1. 擴(kuò)展性好禾蚕。能夠在不修改對(duì)象結(jié)構(gòu)中的元素的情況下,為對(duì)象結(jié)構(gòu)中的元素添加新的功能狂丝。
  2. 復(fù)用性好换淆。可以通過(guò)訪問(wèn)者來(lái)定義整個(gè)對(duì)象結(jié)構(gòu)通用的功能几颜,從而提高系統(tǒng)的復(fù)用程度倍试。
  3. 靈活性好。訪問(wèn)者模式將數(shù)據(jù)結(jié)構(gòu)與作用于結(jié)構(gòu)上的操作解耦蛋哭,使得操作集合可相對(duì)自由地演化而不影響系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)县习。
  4. 符合單一職責(zé)原則。訪問(wèn)者模式把相關(guān)的行為封裝在一起谆趾,構(gòu)成一個(gè)訪問(wèn)者躁愿,使每一個(gè)訪問(wèn)者的功能都比較單一。

缺點(diǎn)

訪問(wèn)者(Visitor)模式的主要缺點(diǎn)如下沪蓬。

  1. 增加新的元素類(lèi)很困難彤钟。在訪問(wèn)者模式中,每增加一個(gè)新的元素類(lèi)怜跑,都要在每一個(gè)具體訪問(wèn)者類(lèi)中增加相應(yīng)的具體操作样勃,這違背了“開(kāi)閉原則”吠勘。
  2. 破壞封裝。訪問(wèn)者模式中具體元素對(duì)訪問(wèn)者公布細(xì)節(jié)峡眶,這破壞了對(duì)象的封裝性剧防。
  3. 違反了依賴倒置原則。訪問(wèn)者模式依賴了具體類(lèi)辫樱,而沒(méi)有依賴抽象類(lèi)峭拘。

應(yīng)用場(chǎng)景

通常在以下情況可以考慮使用訪問(wèn)者(Visitor)模式。

  1. 對(duì)象結(jié)構(gòu)相對(duì)穩(wěn)定狮暑,但其操作算法經(jīng)常變化的程序鸡挠。
  2. 對(duì)象結(jié)構(gòu)中的對(duì)象需要提供多種不同且不相關(guān)的操作,而且要避免讓這些操作的變化影響對(duì)象的結(jié)構(gòu)搬男。
  3. 對(duì)象結(jié)構(gòu)包含很多類(lèi)型的對(duì)象拣展,希望對(duì)這些對(duì)象實(shí)施一些依賴于其具體類(lèi)型的操作。

模式的擴(kuò)展

訪問(wèn)者(Visitor)模式是使用頻率較高的一種設(shè)計(jì)模式缔逛,它常常同以下兩種設(shè)計(jì)模式聯(lián)用备埃。

(1) 與“迭代器模式”聯(lián)用。因?yàn)樵L問(wèn)者模式中的“對(duì)象結(jié)構(gòu)”是一個(gè)包含元素角色的容器褐奴,當(dāng)訪問(wèn)者遍歷容器中的所有元素時(shí)按脚,常常要用迭代器。如案例中的對(duì)象結(jié)構(gòu)是用 List 實(shí)現(xiàn)的敦冬,它通過(guò) List 對(duì)象的 Itemtor() 方法獲取迭代器辅搬。如果對(duì)象結(jié)構(gòu)中的聚合類(lèi)沒(méi)有提供迭代器,也可以用迭代器模式自定義一個(gè)脖旱。

(2) 訪問(wèn)者(Visitor)模式同“組合模式”聯(lián)用堪遂。因?yàn)樵L問(wèn)者(Visitor)模式中的“元素對(duì)象”可能是葉子對(duì)象或者是容器對(duì)象,如果元素對(duì)象包含容器對(duì)象萌庆,就必須用到組合模式蚤氏,其結(jié)構(gòu)圖如下:

image

源碼中的應(yīng)用

javax.lang.model.element.Element
javax.lang.model.element.ElementVisitor
javax.lang.model.type.TypeMirror
javax.lang.model.type.TypeVisitor

PS:以上代碼提交在 Githubhttps://github.com/Niuh-Study/niuh-designpatterns.git

文章持續(xù)更新,可以公眾號(hào)搜一搜「 一角錢(qián)技術(shù) 」第一時(shí)間閱讀踊兜, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄竿滨,歡迎 Star。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捏境,一起剝皮案震驚了整個(gè)濱河市于游,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌垫言,老刑警劉巖贰剥,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異筷频,居然都是意外死亡蚌成,警方通過(guò)查閱死者的電腦和手機(jī)前痘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)担忧,“玉大人芹缔,你說(shuō)我怎么就攤上這事∑渴ⅲ” “怎么了最欠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惩猫。 經(jīng)常有香客問(wèn)我芝硬,道長(zhǎng),這世上最難降的妖魔是什么轧房? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任拌阴,我火速辦了婚禮,結(jié)果婚禮上奶镶,老公的妹妹穿的比我還像新娘皮官。我一直安慰自己,他們只是感情好实辑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著藻丢,像睡著了一般剪撬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悠反,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天残黑,我揣著相機(jī)與錄音,去河邊找鬼斋否。 笑死梨水,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茵臭。 我是一名探鬼主播疫诽,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼旦委!你這毒婦竟也來(lái)了奇徒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缨硝,失蹤者是張志新(化名)和其女友劉穎摩钙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體查辩,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胖笛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年网持,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片长踊。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡功舀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出之斯,到底是詐尸還是另有隱情日杈,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布佑刷,位于F島的核電站莉擒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘫絮。R本人自食惡果不足惜涨冀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望麦萤。 院中可真熱鬧鹿鳖,春花似錦、人聲如沸壮莹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)命满。三九已至涝滴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胶台,已是汗流浹背歼疮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诈唬,地道東北人韩脏。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铸磅,于是被迫代替她去往敵國(guó)和親赡矢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355