java設計模式之訪問者模式(Visitor)

在現實生活中,有些集合對象中存在多種不同的元素,且每種元素也存在多種不同的訪問者和處理方式。例如,公園中存在多個景點,也存在多個遊客,不同的遊客對同一個景點的評價可能不同;醫院醫生開的處方單中包含多種藥元素,査看它的劃價員和藥房工作人員對它的處理方式也不同,劃價員根據處方單上面的藥品名和數量進行劃價,藥房工作人員根據處方單的內容進行抓藥。

這樣的例子還有很多,例如,電影或電視劇中的人物角色,不同的觀衆對他們的評價也不同;還有顧客在商場購物時放在“購物車”中的商品,顧客主要關心所選商品的性價比,而收銀員關心的是商品的價格和數量。

這些被處理的數據元素相對穩定而訪問方式多種多樣的數據結構,如果用“訪問者模式”來處理比較方便。訪問者模式能把處理方法從數據結構中分離出來,並可以根據需要增加新的處理方法,且不用修改原來的程序代碼與數據結構,這提高了程序的擴展性和靈活性。

模式的定義與特點

訪問者(Visitor)模式的定義:將作用於某種數據結構中的各元素的操作分離出來封裝成獨立的類,使其在不改變數據結構的前提下可以添加作用於這些元素的新的操作,爲數據結構中的每個元素提供多種訪問方式。它將對數據的操作與數據結構進行分離,是行爲類模式中最複雜的一種模式。

訪問者(Visitor)模式是一種對象行爲型模式,其主要優點如下。

  1. 擴展性好。能夠在不修改對象結構中的元素的情況下,爲對象結構中的元素添加新的功能。
  2. 複用性好。可以通過訪問者來定義整個對象結構通用的功能,從而提高系統的複用程度。
  3. 靈活性好。訪問者模式將數據結構與作用於結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的數據結構。
  4. 符合單一職責原則。訪問者模式把相關的行爲封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一。


訪問者(Visitor)模式的主要缺點如下。

  1. 增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。
  2. 破壞封裝。訪問者模式中具體元素對訪問者公佈細節,這破壞了對象的封裝性。
  3. 違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。

模式的結構與實現

訪問者(Visitor)模式實現的關鍵是如何將作用於元素的操作分離出來封裝成獨立的類,其基本結構與實現方法如下。

1. 模式的結構

訪問者模式包含以下主要角色。

  1. 抽象訪問者(Visitor)角色:定義一個訪問具體元素的接口,爲每個具體元素類對應一個訪問操作 visit() ,該操作中的參數類型標識了被訪問的具體元素。
  2. 具體訪問者(ConcreteVisitor)角色:實現抽象訪問者角色中聲明的各個訪問操作,確定訪問者訪問一個元素時該做什麼。
  3. 抽象元素(Element)角色:聲明一個包含接受操作 accept() 的接口,被接受的訪問者對象作爲 accept() 方法的參數。
  4. 具體元素(ConcreteElement)角色:實現抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visit(this) ,另外具體元素中可能還包含本身業務邏輯的相關操作。
  5. 對象結構(Object Structure)角色:是一個包含元素角色的容器,提供讓訪問者對象遍歷容器中的所有元素的方法,通常由 List、Set、Map 等聚合類實現。


其結構圖如圖 1 所示。
 

訪問者(Visitor)模式的結構圖
圖1 訪問者(Visitor)模式的結構圖(點此查看原圖

2. 模式的實現

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

package 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)與“迭代器模式”聯用。因爲訪問者模式中的“對象結構”是一個包含元素角色的容器,當訪問者遍歷容器中的所有元素時,常常要用迭代器。如【例1】中的對象結構是用 List 實現的,它通過 List 對象的 Itemtor() 方法獲取迭代器。如果對象結構中的聚合類沒有提供迭代器,也可以用迭代器模式自定義一個。

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

包含組合模式的訪問者模式的結構圖
圖4 包含組合模式的訪問者模式的結構圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章