【設計模式】通過一個簡單的案例理解-訪問者模式(Visitor Pattern)

語言: CN / TW / HK

theme: condensed-night-purple

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第3天,點選檢視活動詳情

場景切入

動物園中有多個場館,比如豹子館,海豚館,大象館等等,有些場館是需要特殊收費的,動物園針對不同型別的遊客有不同的收費方式,比如學生半價。

這個場景下,包括以下要素:動物園動物園中的各個場館不同型別的遊客不同型別的遊客票價不同

動物園就相當於一個物件結構,該結構包含具體的元素-各個場館,每個場館(元素)都有接待遊客(visitor)的方法(accept)。

這些被處理的 資料元素相對穩定 (動物園中的場館一般比較穩定)而 訪問方式多種多樣 (比如學生散客,學生團體,普通遊客,團體遊客等不同的訪問方式)的資料結構,如果用 訪問者模式 來處理比較方便。

訪問者模式能 把處理方法從資料結構中分離出來 ,並可以根據需要增加新的處理方法,且不用修改原來的程式程式碼與資料結構,這提高了程式的擴充套件性和靈活性。

訪問者模式的結構

通過上面場景的分析,訪問者( Visitor )模式實現的關鍵是 如何將作用於元素的操作分離出來封裝成獨立的類 ,其基本結構如下:

  • 抽象的訪問者(Visitor):訪問具體元素的介面,為每個具體元素類對應一個訪問操作 visitXX() ,其引數為某個具體的元素。
  • 具體的訪問者(ConcreteVisitor):實現抽象訪問者角色中宣告的各個訪問操作,確定訪問者訪問一個元素時該做什麼。
  • 抽象元素(Element):宣告一個包含接受操作 accept() 的介面,其引數為訪問者物件(遊客)。
  • 具體元素(ConcreteElement):實現抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visitXX(this) ,另外具體元素中可能還包含本身業務邏輯的相關操作。
  • 物件結構(Object Structure):一個包含元素角色的容器,提供讓訪問者物件遍歷容器中的所有元素的方法,通常由 ListSetMap 等聚合類實現。本例中的 動物園 就可抽象成一個物件結構。

針對設定的動物園場景:

  • 動物園 由多個 場館 組成,是聚合關係;
  • 場館介面 由具體的 獵豹館海豚館 等等具體的場館實現;
  • 訪問者 訪問 場館 ,且其由不同的訪問者進行實現;

根據以上資訊,用訪問者模式實現的類圖為:

程式碼實現

前面已經分析出需要抽象出來的類了,我們把它們轉化成程式碼。

物件結構(Object Structure):

```java //物件結構角色:動物園 class Zoo { //場館集合 private List list = new ArrayList<>();

//接待遊客
public void accept(Visitor visitor) {
    for (ScenerySpot scenerySpot : list) {
        scenerySpot.accept(visitor);
    }
}

public void add(ScenerySpot scenerySpot) {
    list.add(scenerySpot);
}

public void remove(ScenerySpot scenerySpot) {
    list.remove(scenerySpot);
}

} ```

抽象元素和具體元素:

```java //抽象元素:場館景點 interface ScenerySpot { //接待訪問者 void accept(Visitor visitor); //票價(單位是分) Integer ticketRate(); }

//具體元素:豹子館 class LeopardSpot implements ScenerySpot { @Override public void accept(Visitor visitor) { visitor.visitLeopardSpot(this); }

@Override
public Integer ticketRate() {
    //票價15元
    return 1500;
}

}

//具體元素:海豚館 class DolphinSpot implements ScenerySpot { @Override public void accept(Visitor visitor) { visitor.visitDolphinSpot(this); }

@Override
public Integer ticketRate() {
    //票價20元
    return 2000;
}

} ```

抽象訪問者和具體訪問者:

```java //抽象訪問者:遊客 interface Visitor { //參觀獵豹館 void visitLeopardSpot(LeopardSpot leopardSpot); //參觀海豚館 void visitDolphinSpot(DolphinSpot dolphinSpot); }

//具體的訪問者:學生遊客 class StudentVisitor implements Visitor {

@Override
public void visitLeopardSpot(LeopardSpot leopardSpot) {
    //學生票打五折
    int v = (int) (leopardSpot.ticketRate() * 0.5);
    System.out.println("學生遊客遊覽豹子館票價:" + v);
}

@Override
public void visitDolphinSpot(DolphinSpot dolphinSpot) {
    //學生票打五折
    int v = (int) (dolphinSpot.ticketRate() * 0.5);
    System.out.println("學生遊客遊覽海豚館票價:" + v);
}

}

//具體的訪問者:普通遊客 class CommonVisitor implements Visitor {

@Override
public void visitLeopardSpot(LeopardSpot leopardSpot) {
    System.out.println("普通遊客遊覽豹子館票價:" + leopardSpot.ticketRate());
}

@Override
public void visitDolphinSpot(DolphinSpot dolphinSpot) {
    System.out.println("普通遊客遊覽海豚館票價:" + dolphinSpot.ticketRate());
}

} ```

使用:

```java public class VisitorPattern { public static void main(String[] args) { Zoo zoo = new Zoo(); //新增遊覽的場館 zoo.add(new LeopardSpot()); zoo.add(new DolphinSpot()); //還可以新增其他場館

    //動物園接待不同型別的遊客
    //學生遊客
    zoo.accept(new StudentVisitor());
    System.out.println("==========================");
    //普通遊客
    zoo.accept(new CommonVisitor());
    //還可以定義其他型別的遊客,比如公司團體遊客等
}

} ```

執行結果:

學生遊客遊覽豹子館票價:750 學生遊客遊覽海豚館票價:1000 ========================== 普通遊客遊覽豹子館票價:1500 普通遊客遊覽海豚館票價:2000

從程式碼也可以看出來,我們不需要再動 動物園 Zoo 這個結構內部的內容了,需要建造場館實現 ScenerySpot ,或者接待其他型別的遊客實現 Visitor 即可。

應用場景

通常在以下情況可以考慮使用訪問者(Visitor)模式: - 物件結構相對穩定,但其操作演算法經常變化的程式。 - 物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構。 - 物件結構包含很多型別的物件,希望對這些物件實施一些依賴於其具體型別的操作。