深入淺出訪問者模式

huansky發表於2021-03-14

訪問者模式,是行為型設計模式之一。訪問者模式是一種將資料操作與資料結構分離的設計模式,它可以算是 23 中設計模式中最複雜的一個,但它的使用頻率並不是很高,大多數情況下,你並不需要使用訪問者模式,但是當你一旦需要使用它時,那你就是需要使用它了。

訪問者模式的基本想法是,軟體系統中擁有一個由許多物件構成的、比較穩定的物件結構,這些物件的類都擁有一個 accept 方法用來接受訪問者物件的訪問。訪問者是一個介面,它擁有一個 visit 方法,這個方法對訪問到的物件結構中不同型別的元素做出不同的處理。在物件結構的一次訪問過程中,我們遍歷整個物件結構,對每一個元素都實施 accept 方法,在每一個元素的 accept 方法中會呼叫訪問者的 visit 方法,從而使訪問者得以處理物件結構的每一個元素,我們可以針對物件結構設計不同的訪問者類來完成不同的操作,達到區別對待的效果。

定義

封裝一些作用於某種資料結構中的各元素的操作,它可以在不改變這個資料結構的前提下定義作用於這些元素的新的操作。

可以對定義這麼理解:有這麼一個操作,它是作用於一些元素之上的,而這些元素屬於某一個物件結構。同時這個操作是在不改變各元素類的前提下,在這個前提下定義新操作是訪問者模式精髓中的精髓。關鍵程式碼是在資料基礎類裡面有一個方法接受訪問者,將自身引用傳入訪問者。

訪問者(Visitor)模式是一種物件行為型模式,其主要優點如下:

  1. 擴充套件性好。能夠在不修改物件結構中的元素的情況下,為物件結構中的元素新增新的功能。

  2. 複用性好。可以通過訪問者來定義整個物件結構通用的功能,從而提高系統的複用程度。

  3. 靈活性好。訪問者模式將資料結構與作用於結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的資料結構。

  4. 符合單一職責原則。訪問者模式把相關的行為封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一。

主要缺點如下:

  1. 增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。

  2. 破壞封裝。訪問者模式中具體元素對訪問者公佈細節,這破壞了物件的封裝性。

  3. 違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。

基本結構

訪問者(Visitor)模式實現的關鍵是如何將作用於元素的操作分離出來封裝成獨立的類,其 UML 類圖如下:

角色介紹

  • Visitor:介面或者抽象類,定義了對每個 Element 訪問的行為,它的引數就是被訪問的元素,它的方法個數理論上與元素的個數是一樣的,因此,訪問者模式要求元素的型別要穩定,如果經常新增、移除元素類,必然會導致頻繁地修改 Visitor 介面,如果出現這種情況,則說明不適合使用訪問者模式。

  • ConcreteVisitor:具體的訪問者,它需要給出對每一個元素類訪問時所產生的具體行為。

  • Element:元素介面或者抽象類,它定義了一個接受訪問者(accept)的方法,其意義是指每一個元素都要可以被訪問者訪問。

  • ElementA、ElementB:具體的元素類,它提供接受訪問的具體實現,而這個具體的實現,通常情況下是使用訪問者提供的訪問該元素類的方法。

  • ObjectStructure:定義當中所提到的物件結構,物件結構是一個抽象表述,它內部管理了元素集合,並且可以迭代這些元素提供訪問者訪問。

模式的實現

訪問者模式的實現程式碼如下:

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的操作。";
    }
}

//物件結構角色
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);
    }
}

程式的執行結果如下:

具體訪問者A訪問-->具體元素A的操作。
具體訪問者A訪問-->具體元素B的操作。
------------------------
具體訪問者B訪問-->具體元素A的操作。
具體訪問者B訪問-->具體元素B的操作。

模式的應用場景

當系統中存在型別數量穩定(固定)的一類資料結構時,可以使用訪問者模式方便地實現對該型別所有資料結構的不同操作,而又不會對資料產生任何副作用(髒資料)。

簡而言之,就是當對集合中的不同型別資料(型別數量穩定)進行多種操作時,使用訪問者模式。

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

  1. 物件結構相對穩定,但其操作演算法經常變化的程式。

  2. 物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構。

  3. 物件結構包含很多型別的物件,希望對這些物件實施一些依賴於其具體型別的操作。

模式的擴充套件

訪問者(Visitor)模式是使用頻率較高的一種設計模式,它常常同以下兩種設計模式聯用。

  • 與“迭代器模式”聯用。因為訪問者模式中的“物件結構”是一個包含元素角色的容器,當訪問者遍歷容器中的所有元素時,常常要用迭代器。如【例1】中的物件結構是用 List 實現的,它通過 List 物件的 Iterator() 方法獲取迭代器。如果物件結構中的聚合類沒有提供迭代器,也可以用迭代器模式自定義一個。

  • 訪問者(Visitor)模式同“組合模式”聯用。因為訪問者(Visitor)模式中的“元素物件”可能是葉子物件或者是容器物件,如果元素物件包含容器物件,就必須用到組合模式,其結構圖如下圖 所示。

 

總結

其實很早之前也看過訪問者模式,但是當時一直不能理解這個設計模式的實現,感覺就是很難理解。今天在看,其實也沒有那麼難,之所以難還是概念理解不夠深入。

訪問者模式其實關鍵的就是兩個類:Visitor 和 Element。 Element 有個關鍵的方法 accept 方法,表示接受 Visitor 的訪問;Visitor 有個關鍵的的方法是 visit() 方法,引數是 Element,這樣就可以獲取到任何關於 Element 的資訊。然後就可以按照需求隨意組合 Element 提供的資訊。

只要把上面兩個理解清楚了,其他幾個類無非就是介面類,還有個物件結構,整個設計模式的目的就不難理解了。

最後除了理解設計模式的目的,還要理解什麼情況下去用,這樣的話,你對一個設計模式思想就基本就掌握了。

 

參考文章

 

相關文章