【設計模式】詳解訪問者(Visitor)模式-有多段程式碼出沒

traveler100發表於2020-09-29

場景切入

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

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

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

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

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

訪問者模式的結構

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

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

針對我之前設定的動物園場景,用訪問者模式實現的類圖為:

在這裡插入圖片描述

程式碼實現

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

物件結構(Object Structure):

//物件結構角色:動物園
class Zoo {
    //場館集合
    private List<ScenerySpot> 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);
    }
}

抽象元素和具體元素:

//抽象元素:場館景點
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;
    }
}

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

//抽象訪問者:遊客
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());
    }
}

使用:

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)模式:

  • 物件結構相對穩定,但其操作演算法經常變化的程式。
  • 物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構。
  • 物件結構包含很多型別的物件,希望對這些物件實施一些依賴於其具體型別的操作。

其他設計模式閱讀

您的點贊,我的動力!
本文已收錄github:https://github.com/xblzer/JavaJourney
微信搜公眾號 行百里er,獲取更多幹貨文章。
在這裡插入圖片描述

相關文章