深入淺出訪問者模式
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
作者 | huansky
來源 | urlify.cn/aEjABf
訪問者模式,是行為型設(shè)計模式之一。訪問者模式是一種將數(shù)據(jù)操作與數(shù)據(jù)結(jié)構(gòu)分離的設(shè)計模式,它可以算是 23 中設(shè)計模式中最復(fù)雜的一個,但它的使用頻率并不是很高,大多數(shù)情況下,你并不需要使用訪問者模式,但是當(dāng)你一旦需要使用它時,那你就是需要使用它了。
訪問者模式的基本想法是,軟件系統(tǒng)中擁有一個由許多對象構(gòu)成的、比較穩(wěn)定的對象結(jié)構(gòu),這些對象的類都擁有一個 accept 方法用來接受訪問者對象的訪問。訪問者是一個接口,它擁有一個 visit 方法,這個方法對訪問到的對象結(jié)構(gòu)中不同類型的元素做出不同的處理。在對象結(jié)構(gòu)的一次訪問過程中,我們遍歷整個對象結(jié)構(gòu),對每一個元素都實施 accept 方法,在每一個元素的 accept 方法中會調(diào)用訪問者的 visit 方法,從而使訪問者得以處理對象結(jié)構(gòu)的每一個元素,我們可以針對對象結(jié)構(gòu)設(shè)計不同的訪問者類來完成不同的操作,達(dá)到區(qū)別對待的效果。
定義
封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作,它可以在不改變這個數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
可以對定義這么理解:有這么一個操作,它是作用于一些元素之上的,而這些元素屬于某一個對象結(jié)構(gòu)。同時這個操作是在不改變各元素類的前提下,在這個前提下定義新操作是訪問者模式精髓中的精髓。關(guān)鍵代碼是在數(shù)據(jù)基礎(chǔ)類里面有一個方法接受訪問者,將自身引用傳入訪問者。
訪問者(Visitor)模式是一種對象行為型模式,其主要優(yōu)點如下:
擴(kuò)展性好。能夠在不修改對象結(jié)構(gòu)中的元素的情況下,為對象結(jié)構(gòu)中的元素添加新的功能。
復(fù)用性好??梢酝ㄟ^訪問者來定義整個對象結(jié)構(gòu)通用的功能,從而提高系統(tǒng)的復(fù)用程度。
靈活性好。訪問者模式將數(shù)據(jù)結(jié)構(gòu)與作用于結(jié)構(gòu)上的操作解耦,使得操作集合可相對自由地演化而不影響系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)。
符合單一職責(zé)原則。訪問者模式把相關(guān)的行為封裝在一起,構(gòu)成一個訪問者,使每一個訪問者的功能都比較單一。
主要缺點如下:
增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應(yīng)的具體操作,這違背了“開閉原則”。
破壞封裝。訪問者模式中具體元素對訪問者公布細(xì)節(jié),這破壞了對象的封裝性。
違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。
基本結(jié)構(gòu)
訪問者(Visitor)模式實現(xiàn)的關(guān)鍵是如何將作用于元素的操作分離出來封裝成獨立的類,其 UML 類圖如下:

角色介紹
Visitor:接口或者抽象類,定義了對每個 Element 訪問的行為,它的參數(shù)就是被訪問的元素,它的方法個數(shù)理論上與元素的個數(shù)是一樣的,因此,訪問者模式要求元素的類型要穩(wěn)定,如果經(jīng)常添加、移除元素類,必然會導(dǎo)致頻繁地修改 Visitor 接口,如果出現(xiàn)這種情況,則說明不適合使用訪問者模式。
ConcreteVisitor:具體的訪問者,它需要給出對每一個元素類訪問時所產(chǎn)生的具體行為。
Element:元素接口或者抽象類,它定義了一個接受訪問者(accept)的方法,其意義是指每一個元素都要可以被訪問者訪問。
ElementA、ElementB:具體的元素類,它提供接受訪問的具體實現(xiàn),而這個具體的實現(xiàn),通常情況下是使用訪問者提供的訪問該元素類的方法。
ObjectStructure:定義當(dāng)中所提到的對象結(jié)構(gòu),對象結(jié)構(gòu)是一個抽象表述,它內(nèi)部管理了元素集合,并且可以迭代這些元素提供訪問者訪問。
模式的實現(xiàn)
訪問者模式的實現(xiàn)代碼如下:
package net.biancheng.c.visitor;
import java.util.*;
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);
}
}
//抽象訪問者
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
//具體訪問者A類
class ConcreteVisitorA implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具體訪問者A訪問-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具體訪問者A訪問-->" + element.operationB());
}
}
//具體訪問者B類
class ConcreteVisitorB implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具體訪問者B訪問-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具體訪問者B訪問-->" + element.operationB());
}
}
//抽象元素類
interface Element {
void accept(Visitor visitor);
}
//具體元素A類
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具體元素A的操作。";
}
}
//具體元素B類
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具體元素B的操作。";
}
}
//對象結(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é)果如下:
具體訪問者A訪問-->具體元素A的操作。
具體訪問者A訪問-->具體元素B的操作。
------------------------
具體訪問者B訪問-->具體元素A的操作。
具體訪問者B訪問-->具體元素B的操作。模式的應(yīng)用場景
當(dāng)系統(tǒng)中存在類型數(shù)量穩(wěn)定(固定)的一類數(shù)據(jù)結(jié)構(gòu)時,可以使用訪問者模式方便地實現(xiàn)對該類型所有數(shù)據(jù)結(jié)構(gòu)的不同操作,而又不會對數(shù)據(jù)產(chǎn)生任何副作用(臟數(shù)據(jù))。
簡而言之,就是當(dāng)對集合中的不同類型數(shù)據(jù)(類型數(shù)量穩(wěn)定)進(jìn)行多種操作時,使用訪問者模式。
通常在以下情況可以考慮使用訪問者(Visitor)模式。
對象結(jié)構(gòu)相對穩(wěn)定,但其操作算法經(jīng)常變化的程序。
對象結(jié)構(gòu)中的對象需要提供多種不同且不相關(guān)的操作,而且要避免讓這些操作的變化影響對象的結(jié)構(gòu)。
對象結(jié)構(gòu)包含很多類型的對象,希望對這些對象實施一些依賴于其具體類型的操作。
模式的擴(kuò)展
訪問者(Visitor)模式是使用頻率較高的一種設(shè)計模式,它常常同以下兩種設(shè)計模式聯(lián)用。
與“迭代器模式”聯(lián)用。因為訪問者模式中的“對象結(jié)構(gòu)”是一個包含元素角色的容器,當(dāng)訪問者遍歷容器中的所有元素時,常常要用迭代器。如【例1】中的對象結(jié)構(gòu)是用 List 實現(xiàn)的,它通過 List 對象的 Iterator() 方法獲取迭代器。如果對象結(jié)構(gòu)中的聚合類沒有提供迭代器,也可以用迭代器模式自定義一個。
訪問者(Visitor)模式同“組合模式”聯(lián)用。因為訪問者(Visitor)模式中的“元素對象”可能是葉子對象或者是容器對象,如果元素對象包含容器對象,就必須用到組合模式,其結(jié)構(gòu)圖如下圖 所示。
總結(jié)
其實很早之前也看過訪問者模式,但是當(dāng)時一直不能理解這個設(shè)計模式的實現(xiàn),感覺就是很難理解。今天在看,其實也沒有那么難,之所以難還是概念理解不夠深入。
訪問者模式其實關(guān)鍵的就是兩個類:Visitor 和 Element。Element 有個關(guān)鍵的方法 accept 方法,表示接受 Visitor 的訪問;Visitor 有個關(guān)鍵的的方法是 visit() 方法,參數(shù)是 Element,這樣就可以獲取到任何關(guān)于 Element 的信息。然后就可以按照需求隨意組合 Element 提供的信息。
只要把上面兩個理解清楚了,其他幾個類無非就是接口類,還有個對象結(jié)構(gòu),整個設(shè)計模式的目的就不難理解了。
最后除了理解設(shè)計模式的目的,還要理解什么情況下去用,這樣的話,你對一個設(shè)計模式思想就基本就掌握了。
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼 2 秒
感謝點贊支持下哈 
