摘要:概述訪問(wèn)者模式預(yù)留通路,回調(diào)實(shí)現(xiàn)。具體訪問(wèn)者給出對(duì)每一個(gè)元素類訪問(wèn)時(shí)所產(chǎn)生的具體行為。靜態(tài)分派通過(guò)方法重載支持靜態(tài)分派。上古上古第一代子孫上古第二代子孫顯然,類的方法是由三個(gè)方法重載而成的。因?yàn)樵L問(wèn)者模式使得算法操作增加變得容易。
訪問(wèn)者模式(Visitor Pattern)屬于對(duì)象行為型模式的一種,表示一個(gè)作用于其對(duì)象結(jié)構(gòu)中的各元素的操作,它使你可以在不改變各元素類的前提下定義作用于這些元素的新操作。概述
訪問(wèn)者模式: 預(yù)留通路,回調(diào)實(shí)現(xiàn)。它的實(shí)現(xiàn)主要就是通過(guò)預(yù)先定義好調(diào)用的通路,在被訪問(wèn)的對(duì)象上定義accept方法,在訪問(wèn)者的對(duì)象上定義visit方法;然后在調(diào)用真正發(fā)生的時(shí)候,通過(guò)兩次分發(fā)的技術(shù),利用預(yù)先定義好的通路,回調(diào)到訪問(wèn)者具體的實(shí)現(xiàn)上。
主要解決
現(xiàn)已穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問(wèn)題,把數(shù)據(jù)結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦合,使得操作集合可相對(duì)自由地演化。
UML結(jié)構(gòu)圖
模式結(jié)構(gòu)
抽象訪問(wèn)者(Visitor): 定義了對(duì)每一個(gè)元素(Element)訪問(wèn)的行為,它的參數(shù)就是可以訪問(wèn)的元素,它的方法個(gè)數(shù)理論上來(lái)講與元素個(gè)數(shù)(Element的實(shí)現(xiàn)類個(gè)數(shù))是一樣的,從這點(diǎn)不難看出,訪問(wèn)者模式要求元素類的個(gè)數(shù)不能改變(不能改變的意思是說(shuō),如果元素類的個(gè)數(shù)經(jīng)常改變,則說(shuō)明不適合使用訪問(wèn)者模式)。
具體訪問(wèn)者(ConcreteVisitor): 給出對(duì)每一個(gè)元素類訪問(wèn)時(shí)所產(chǎn)生的具體行為。
抽象節(jié)點(diǎn)(Element): 定義了一個(gè)接受訪問(wèn)者(accept)的方法,其意義是指,每一個(gè)元素都要可以被訪問(wèn)者訪問(wèn)。
具體節(jié)點(diǎn)(ConcreteElement): 提供接受訪問(wèn)方法的具體實(shí)現(xiàn),而這個(gè)具體的實(shí)現(xiàn),通常情況下是使用訪問(wèn)者提供的訪問(wèn)該元素類的方法。
結(jié)構(gòu)對(duì)象角色(ObjectStructure): 定義當(dāng)中所提到的對(duì)象結(jié)構(gòu),對(duì)象結(jié)構(gòu)是一個(gè)抽象表述,具體點(diǎn)可以理解為一個(gè)具有容器性質(zhì)或者復(fù)合對(duì)象特性的類,它會(huì)含有一組元素(Element),并且可以迭代這些元素,供訪問(wèn)者訪問(wèn)。
案例場(chǎng)景:很多人都有養(yǎng)寵物的習(xí)慣,這里就以此為例
訪問(wèn)者角色:給寵物喂食的人
具體訪問(wèn)者角色:主人、其他人
抽象元素角色:動(dòng)物抽象類
具體元素角色:寵物狗、寵物貓
結(jié)構(gòu)對(duì)象角色:主人家
結(jié)構(gòu)圖如下:
1.創(chuàng)建抽象訪問(wèn)者接口
interface Person { void feed(Cat cat); void feed(Dog dog); }
2.創(chuàng)建不同的具體訪問(wèn)者角色 -- 主人/其他人,同時(shí)實(shí)現(xiàn) Person接口
class Owner implements Person { @Override public void feed(Cat cat) { System.out.println("主人喂食貓"); } @Override public void feed(Dog dog) { System.out.println("主人喂食狗"); } } class Someone implements Person { @Override public void feed(Cat cat) { System.out.println("其他人喂食貓"); } @Override public void feed(Dog dog) { System.out.println("其他人喂食狗"); } }
3.創(chuàng)建 抽象節(jié)點(diǎn) -- 寵物
interface Animal { void accept(Person person); }
4.創(chuàng)建實(shí)現(xiàn)Animal接口的 具體節(jié)點(diǎn)(元素)
class Dog implements Animal { @Override public void accept(Person person) { person.feed(this); System.out.println("好好吃,汪汪汪?。。?); } } /** * 具體節(jié)點(diǎn)(元素)角色 -- 寵物貓 */ class Cat implements Animal { @Override public void accept(Person person) { person.feed(this); System.out.println("好好吃,喵喵喵?。?!"); } }
5.創(chuàng)建實(shí)現(xiàn)Animal接口的 具體節(jié)點(diǎn)(元素)
class Home { private ListnodeList = new ArrayList<>(); void action(Person person) { for (Animal node : nodeList) { node.accept(person); } } /** * 添加操作 * * @param animal 動(dòng)物 */ void add(Animal animal) { nodeList.add(animal); } }
6.創(chuàng)建客戶端,用于測(cè)試
public class Client { public static void main(String[] args) { Home home = new Home(); home.add(new Dog()); home.add(new Cat()); Owner owner = new Owner(); home.action(owner); Someone someone = new Someone(); home.action(someone); } }
7.運(yùn)行結(jié)果
主人喂食狗 好好吃,汪汪汪?。。?主人喂食貓 好好吃,喵喵喵!??! 其他人喂食狗 好好吃,汪汪汪?。?! 其他人喂食貓 好好吃,喵喵喵!?。?/pre> 分派變量被聲明時(shí)的類型叫做變量的靜態(tài)類型(Static Type),有些人又把靜態(tài)類型叫做明顯類型(Apparent Type);而變量所引用的對(duì)象的真實(shí)類型又叫做變量的實(shí)際類型(Actual Type)。
比如:
Map map = null; map = new HashMap();聲明了一個(gè)變量map,它的靜態(tài)類型(也叫明顯類型)是Map,而它的實(shí)際類型是HashMap。
根據(jù)對(duì)象的類型而對(duì)方法進(jìn)行的選擇,就是分派(Dispatch),分派(Dispatch)又分為兩種,即靜態(tài)分派和動(dòng)態(tài)分派。
靜態(tài)分派(Static Dispatch) 發(fā)生在編譯時(shí)期,分派根據(jù)靜態(tài)類型信息發(fā)生。靜態(tài)分派對(duì)于我們來(lái)說(shuō)并不陌生,方法重載就是靜態(tài)分派。
動(dòng)態(tài)分派(Dynamic Dispatch) 發(fā)生在運(yùn)行時(shí)期,動(dòng)態(tài)分派動(dòng)態(tài)地置換掉某個(gè)方法。
靜態(tài)分派:Java通過(guò)方法重載支持靜態(tài)分派。
動(dòng)態(tài)分派:Java通過(guò)方法的重寫(xiě)支持動(dòng)態(tài)分派。
動(dòng)態(tài)分派通過(guò)方法的重寫(xiě)支持動(dòng)態(tài)分派。
class Dog { public void execute() { System.out.println("上古 Dog"); } } class DogBaby1 extends Dog { @Override public void execute() { System.out.println("上古Dog第一代子孫"); } } class DogBaby2 extends Dog { @Override public void execute() { System.out.println("上古Dog第二代子孫"); } } /** * @author Levin */ public class Client { public static void main(String[] args) { Dog baby1 = new DogBaby1(); baby1.execute(); Dog baby2 = new DogBaby2(); baby2.execute(); } }變量baby1的靜態(tài)類型是Dog,而真實(shí)類型是DogBaby1。
execute()方法調(diào)用的是DogBaby1類的execute()方法,那么上面打印的就是 上古Dog第一代子孫;
變量baby2的靜態(tài)類型是Dog,而真實(shí)類型是DogBaby2。
execute()方法調(diào)用的是DogBaby2類的execute()方法,那么上面打印的就是 上古Dog第二代子孫;
所以,問(wèn)題的核心就是Java編譯器在編譯時(shí)期并不總是知道哪些代碼會(huì)被執(zhí)行,因?yàn)榫幾g器僅僅知道對(duì)象的靜態(tài)類型,而不知道對(duì)象的真實(shí)類型;而方法的調(diào)用則是根據(jù)對(duì)象的真實(shí)類型,而不是靜態(tài)類型。
靜態(tài)分派通過(guò)方法重載支持靜態(tài)分派。
class Dog { } class DogBaby1 extends Dog { } class DogBaby2 extends Dog { } class Execute { public void execute(Dog dog) { System.out.println("上古 Dog"); } public void execute(DogBaby1 baby1) { System.out.println("上古Dog第一代子孫"); } public void execute(DogBaby2 baby2) { System.out.println("上古Dog第二代子孫"); } } /** * @author Levin * @create 2017/12/19 0019 */ public class Client { public static void main(String[] args) { Dog dog = new Dog(); Dog baby1 = new DogBaby1(); Dog baby2 = new DogBaby2(); Execute exe = new Execute(); exe.execute(dog); exe.execute(baby1); exe.execute(baby2); } }顯然,Execute類的excute()方法是由三個(gè)方法重載而成的。這三個(gè)方法分別接受狗(Dog)、狗baby1(DogBaby1)、狗baby2(DogBaby2)等類型的參數(shù)。
運(yùn)行結(jié)果上古 Dog 上古 Dog 上古 Dog為什么呢?三次對(duì)execute()方法的調(diào)用傳入的是不同的參數(shù),分別是dog、baby1、baby2。它們雖然具有不同的真實(shí)類型,但是它們的靜態(tài)類型都是一樣的,均是Dog類型。
重載方法的分派是根據(jù)靜態(tài)類型進(jìn)行的,這個(gè)分派過(guò)程在編譯時(shí)期就完成了。
雙(重)分派Java是靜態(tài)多分派、動(dòng)態(tài)單分派的語(yǔ)言。
Java不支持動(dòng)態(tài)的雙分派。但是通過(guò)使用設(shè)計(jì)模式,也可以在Java語(yǔ)言里實(shí)現(xiàn)動(dòng)態(tài)的雙重分派。
首先,什么是雙分派?還記得 設(shè)計(jì)模式解密(22)- 訪問(wèn)者模式 中舉的例子嗎?
訪問(wèn)者模式用到了一種雙分派的技術(shù),所謂雙分派技術(shù)就是在選擇一個(gè)方法的時(shí)候,不僅僅要根據(jù)消息接收者(receiver)的運(yùn)行時(shí)區(qū)別(Run time type),還要根據(jù)參數(shù)的運(yùn)行時(shí)區(qū)別。
在訪問(wèn)者模式中,客戶端將具體狀態(tài)當(dāng)做參數(shù)傳遞給具體訪問(wèn)者,這里完成第一次分派,然后具體訪問(wèn)者作為參數(shù)的具體狀態(tài)中的方法,同時(shí)也將自己this作為參數(shù)傳遞進(jìn)去,這里就完成了第二次分派。雙分派意味著得到的執(zhí)行操作決定于請(qǐng)求的種類和接受者的類型。
雙分派的核心就是這個(gè)this對(duì)象。
說(shuō)到這里,我們已經(jīng)明白雙分派是怎么回事了,但是它有什么效果呢?就是可以實(shí)現(xiàn)方法的動(dòng)態(tài)綁定,我們可以對(duì)上面的程序進(jìn)行修改。
class Dog { public void accept(Execute exe) { exe.execute(this); } } class DogBaby1 extends Dog { @Override public void accept(Execute exe) { exe.execute(this); } } class DogBaby2 extends Dog { @Override public void accept(Execute exe) { exe.execute(this); } } class Execute { public void execute(Dog dog) { System.out.println("上古 Dog"); } public void execute(DogBaby1 baby1) { System.out.println("上古Dog第一代子孫"); } public void execute(DogBaby2 baby2) { System.out.println("上古Dog第二代子孫"); } } /** * 雙重分派 * * @author Levin * @create 2017/12/19 0019 */ public class Client { public static void main(String[] args) { Dog dog = new Dog(); Dog baby1 = new DogBaby1(); Dog baby2 = new DogBaby2(); Execute exe = new Execute(); dog.accept(exe); baby1.accept(exe); baby2.accept(exe); } }運(yùn)行結(jié)果上古 Dog 上古Dog第一代子孫 上古Dog第二代子孫從結(jié)果可以看出:雙分派實(shí)現(xiàn)動(dòng)態(tài)綁定的本質(zhì),就是在重載方法委派的前面加上了繼承體系中覆蓋的環(huán)節(jié),由于覆蓋是動(dòng)態(tài)的,所以重載就是動(dòng)態(tài)的了?。。?/strong>
總結(jié)訪問(wèn)者模式把數(shù)據(jù)結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦合,使得操作集合可相對(duì)自由地演化。訪問(wèn)者模式適用于數(shù)據(jù)結(jié)構(gòu)相對(duì)穩(wěn)定算法又易變化的系統(tǒng)。因?yàn)樵L問(wèn)者模式使得算法操作增加變得容易。若系統(tǒng)數(shù)據(jù)結(jié)構(gòu)對(duì)象易于變化,經(jīng)常有新的數(shù)據(jù)對(duì)象增加進(jìn)來(lái),則不適合使用訪問(wèn)者模式。
優(yōu)點(diǎn)擴(kuò)展性好: 在不修改對(duì)象結(jié)構(gòu)中的元素的情況下,為對(duì)象結(jié)構(gòu)中的元素添加新的功能。
復(fù)用性好: 通過(guò)訪問(wèn)者來(lái)定義整個(gè)對(duì)象結(jié)構(gòu)通用的功能,從而提高復(fù)用程度。
分離無(wú)關(guān)行為: 通過(guò)訪問(wèn)者來(lái)分離無(wú)關(guān)的行為,把相關(guān)的行為封裝在一起,構(gòu)成一個(gè)訪問(wèn)者,這樣每一個(gè)訪問(wèn)者的功能都比較單一。
缺點(diǎn)對(duì)象結(jié)構(gòu)變化很困難: 不適用于對(duì)象結(jié)構(gòu)中的類經(jīng)常變化的情況,因?yàn)閷?duì)象結(jié)構(gòu)發(fā)生了改變,訪問(wèn)者的接口和訪問(wèn)者的實(shí)現(xiàn)都要發(fā)生相應(yīng)的改變,代價(jià)太高。
破壞封裝: 訪問(wèn)者模式通常需要對(duì)象結(jié)構(gòu)開(kāi)放內(nèi)部數(shù)據(jù)給訪問(wèn)者和ObjectStructrue,這破壞了對(duì)象的封裝性。
使用場(chǎng)景數(shù)據(jù)結(jié)構(gòu)穩(wěn)定,作用于數(shù)據(jù)結(jié)構(gòu)的操作經(jīng)常變化的時(shí)候。
當(dāng)一個(gè)數(shù)據(jù)結(jié)構(gòu)中,一些元素類需要負(fù)責(zé)與其不相關(guān)的操作的時(shí)候,為了將這些操作分離出去,以減少這些元素類的職責(zé)時(shí),可以使用訪問(wèn)者模式。
有時(shí)在對(duì)數(shù)據(jù)結(jié)構(gòu)上的元素進(jìn)行操作的時(shí)候,需要區(qū)分具體的類型,這時(shí)使用訪問(wèn)者模式可以針對(duì)不同的類型,在訪問(wèn)者類中定義不同的操作,從而去除掉類型判斷。
說(shuō)點(diǎn)什么參考文獻(xiàn):http://www.cnblogs.com/JsonShare/p/7380772.html
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter19/battcn-visitor
個(gè)人QQ:1837307557
battcn開(kāi)源群(適合新手):391619659
微信公眾號(hào):battcn(歡迎調(diào)戲)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70843.html
摘要:組合模式的圖組成部分組合對(duì)象為組合中的對(duì)象聲明接口,在適當(dāng)?shù)那闆r下,實(shí)現(xiàn)所有類共有接口的默認(rèn)行為,聲明用于訪問(wèn)和管理其子組件的接口。組合模式對(duì)單個(gè)對(duì)象葉子對(duì)象和組合對(duì)象容器對(duì)象的使用具有一致性。 組合模式(Composite Pattern)屬于結(jié)構(gòu)型模式的一種,組合多個(gè)對(duì)象形成樹(shù)形結(jié)構(gòu)來(lái)表示部分 - 整體的結(jié)構(gòu)層次,對(duì)單個(gè)對(duì)象(葉子對(duì)象)和組合對(duì)象(容器對(duì)象)的使用具有一致性 概述...
摘要:備忘錄模式常常與命令模式和迭代子模式一同使用。自述歷史所謂自述歷史模式實(shí)際上就是備忘錄模式的一個(gè)變種。在備忘錄模式中,發(fā)起人角色負(fù)責(zé)人角色和備忘錄角色都是獨(dú)立的角色。 備忘錄模式(Memento Pattern)屬于行為型模式的一種,在不破壞封裝特性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)。這樣就可以將該對(duì)象恢復(fù)到原先保存的狀態(tài)。 概述 備忘錄模式又叫做快照模式(...
摘要:迭代器模式屬于行為型模式的一種,提供一種方法訪問(wèn)一個(gè)容器中各個(gè)元素,而又不需要暴露該對(duì)象的內(nèi)部細(xì)節(jié)。迭代器模式把在元素之間游走的責(zé)任交給迭代器,而不是聚合對(duì)象。 迭代器模式(Iterator Pattern)屬于行為型模式的一種,提供一種方法訪問(wèn)一個(gè)容器中各個(gè)元素,而又不需要暴露該對(duì)象的內(nèi)部細(xì)節(jié)。 概述 迭代器模式聽(tīng)起來(lái)可能感覺(jué)很陌生,但是實(shí)際上,迭代器模式是所有設(shè)計(jì)模式中最簡(jiǎn)單也是...
摘要:懶漢非線程安全,需要用一定的風(fēng)騷操作控制,裝逼失敗有可能導(dǎo)致看一周的海綿寶寶餓漢天生線程安全,的時(shí)候就已經(jīng)實(shí)例化好,該操作過(guò)于風(fēng)騷會(huì)造成資源浪費(fèi)單例注冊(cè)表初始化的時(shí)候,默認(rèn)單例用的就是該方式特點(diǎn)私有構(gòu)造方法,只能有一個(gè)實(shí)例。 單例設(shè)計(jì)模式(Singleton Pattern)是最簡(jiǎn)單且常見(jiàn)的設(shè)計(jì)模式之一,主要作用是提供一個(gè)全局訪問(wèn)且只實(shí)例化一次的對(duì)象,避免多實(shí)例對(duì)象的情況下引起邏輯性錯(cuò)...
摘要:適配器是將接口轉(zhuǎn)換為不同接口,而外觀模式是提供一個(gè)統(tǒng)一的接口來(lái)簡(jiǎn)化接口。 外觀模式(Facade Pattern)屬于結(jié)構(gòu)型模式的一種,為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一的入口,它通過(guò)引入一個(gè)外觀角色來(lái)簡(jiǎn)化客戶端與子系統(tǒng)之間的交互... 概述 外觀模式是一種使用頻率非常高的結(jié)構(gòu)型設(shè)計(jì)模式,當(dāng)你要為一個(gè)復(fù)雜子系統(tǒng)提供一個(gè)簡(jiǎn)單接口時(shí)。子系統(tǒng)往往因?yàn)椴粩嘌莼兊迷絹?lái)越復(fù)雜。大多數(shù)模式使用時(shí)...
閱讀 1981·2019-08-30 15:54
閱讀 3608·2019-08-29 13:07
閱讀 3133·2019-08-29 12:39
閱讀 1799·2019-08-26 12:13
閱讀 1555·2019-08-23 18:31
閱讀 2167·2019-08-23 18:05
閱讀 1856·2019-08-23 18:00
閱讀 1052·2019-08-23 17:15