設計模式之訪問者模式

訪問者模式(Visitor Pattern):在不改變某對象結構中元素的前提下,定義作用於這些元素的新操作。

訪問者模式適用於數據結構相對穩定的系統,可以把數據結構和作用於結構上的操作解耦,使操作集合可以相對自由地變化。一種操作就相當於一個訪問者。訪問者模式將有關操作行爲集中到一個訪問者對象中。

訪問者模式的缺點在於會使數據結構的變化變得困難。

比如男人和女人對於不同的事件有不同的反應,如果單純抽象出Person基類,Man和Woman根據傳遞進來的不同事件作出不同反應,這樣新增事件的話都要修改Man和Woman,違反了封閉開放原則。

可以把變化的地方,也就是不同的事件,抽象一個基類,然後基於這個基類進行擴展,可以達到比較好的解耦效果。

實例實現

首先定義數據基類,基本方法就是對事件進行反應:

public interface Person {
    void react(Event visitor);
}

兩個數據類,訪問者模式一般應用於數據元素比較穩定的情況下:

class Man implements Person {
    @Override
    public void react(Event visitor) {
        visitor.getManReaction(this);
    }
}

class Woman implements Person {
    @Override
    public void react(Event visitor) {
        visitor.getWomanReaction(this);
    }
}

此處用到一個技術叫雙分派,就是在數據對象的一個方法中,接收事件對象作爲參數,這是一次分派;然後接收到的事件對象調用自己的方法,傳入調用它的數據對象作爲方法參數,這就是第二次分派。雙分派技術意味着方法的執行不僅取決於方法參數,還取決於執行方法的對象,由方法參數和調用者本身共同決定執行結果。

然後定義事件類,也就是模式裏的訪問者,事件類就是定義數據類各自的反應方法,如果數據元素穩定不變的話,事件類的方法也就不用去修改:

public interface Event {
    void getManReaction(Man man);

    void getWomanReaction(Woman woman);
}

事件類的具體實現:

class Success implements Event {
    @Override
    public void getManReaction(Man man) {
        System.out.println(man.getClass().getSimpleName() + " "
                + this.getClass().getSimpleName() + "時,背後一般有個偉大的女人");
    }

    @Override
    public void getWomanReaction(Woman woman) {
        System.out.println(woman.getClass().getSimpleName() + " "
                + this.getClass().getSimpleName() + "時,背後一般有個不成功的男人");
    }
}

class Failure implements Event {
    @Override
    public void getManReaction(Man man) {
        System.out.println(man.getClass().getSimpleName() + " "
                + this.getClass().getSimpleName() + "時,獨自借酒澆愁");
    }

    @Override
    public void getWomanReaction(Woman woman) {
        System.out.println(woman.getClass().getSimpleName() + " "
                + this.getClass().getSimpleName() + "時,需要人來安慰");
    }
}

class Marriage implements Event {
    @Override
    public void getManReaction(Man man) {
        System.out.println(man.getClass().getSimpleName() + " "
                + this.getClass().getSimpleName() + "時,我會照顧你一輩子");
    }

    @Override
    public void getWomanReaction(Woman woman) {
        System.out.println(woman.getClass().getSimpleName() + " "
                + this.getClass().getSimpleName() + "時,我願意和你到天荒地老");
    }
}

然後我們還可以定義一個數據結構,用來組織數據元素對於不同訪問者的反應結果:

public class DataStructure {
    private List<Person> elements = new ArrayList<>();

    public void addElement(Person element) {
        elements.add(element);
    }

    public void removeElement(Person element) {
        elements.remove(element);
    }

    public void showReaction(Event visitor) {
        for (Person person : elements) {
            person.react(visitor);
        }
    }
}

測試類:

public class Main {
    public static void main(String[] args) {
        final DataStructure dataStructure = new DataStructure();

        final Man man = new Man();
        final Woman woman = new Woman();
        dataStructure.addElement(man);
        dataStructure.addElement(woman);

        dataStructure.showReaction(new Success());
        System.out.println();
        dataStructure.showReaction(new Failure());
        System.out.println();
        dataStructure.showReaction(new Marriage());
    }
}

輸出:

Man Success時,背後一般有個偉大的女人
Woman Success時,背後一般有個不成功的男人

Man Failure時,獨自借酒澆愁
Woman Failure時,需要人來安慰

Man Marriage時,我會照顧你一輩子
Woman Marriage時,我願意和你到天荒地老

訪問者模式使得數據元素不變的情況下,新增事件只要創建新的實現即可,不用修改原有代碼,很好地符合了封閉開放原則。

發佈了73 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章