訪問者模式

假設(shè)有男人和女人兩種元素,要分別打印出他們在不同狀態(tài)時的不同表現(xiàn)。

用OO的思想把表現(xiàn)(行為)提取出來作為一個抽象方法,代碼如下:

用if-else對狀態(tài)進行判斷
Person接口

    public interface Person {
          public void action(String state);
    }


Man實現(xiàn)類


Java代碼   收藏代碼

    public class Man implements Person{
     
        public void action(String state) {
            if(state == "success"){
                System.out.println("當男人成功時,背后多半有一個偉大的女人");
            }
            else if(state == "love"){
                System.out.println("當男人戀愛時,凡事不懂也裝懂");
            }
        }   
    }

Woman實現(xiàn)類

 
Java代碼   收藏代碼

    public class Woman implements Person{
     
        public void action(String state) {
            if(state == "success"){
                System.out.println("當女人成功時,背后大多有一個不成功的男人");
            }
            else if(state == "love"){
                System.out.println("當女人戀愛時,遇事懂也裝不懂");
            }
        }
     
    }

客戶端測試代碼

 
Java代碼   收藏代碼

 

    public class Client {
         public static void main(String[] args) {
            Person man = new Man();
            Person woman = new Woman();
            man.action("success");
            woman.action("success");
            
            man.action("love");
            woman.action("love");
        }
    }

結(jié)果顯示:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂


當需求發(fā)生變化時,要增加一種失敗狀態(tài)時,增加男人女人的不同表現(xiàn),這時就要修改Man類與Woman類的if else,違反了ocp原則(對增加開放-對修改封閉)。而且隨著需求的增加,Man類與Woman類的if,else越來越臃腫,需要取消時,又要去修改if,else,既不方便,又容易出錯。
這時候訪問者模式可以派上用場了。
請看下面經(jīng)修改后的類圖:

使用訪問者模式

把狀態(tài)抽象出來成為一個接口(訪問者),不同的狀態(tài)就作為狀態(tài)的不同實現(xiàn)類(不同的訪問者)。
狀態(tài)的接口(訪問者接口)

 

    public interface Visitor {
          public void visit(Man man);
          public void visit(Woman woman);
    }

具體訪問者實現(xiàn)類(分別代表不同的狀態(tài))

 
Java代碼   收藏代碼

    //成功時Man與Woman的不同表現(xiàn)
    public class Success implements Visitor{
     
        public void visit(Man man) {
            System.out.println("當男人成功時,背后多半有一個偉大的女人");
        }
     
        public void visit(Woman woman) {
            System.out.println("當女人成功時,背后大多有一個不成功的男人");
        }
    }
     
     
    public class Love implements Visitor{
     
        public void visit(Man man) {
            System.out.println("當男人戀愛時,凡事不懂也裝懂");
        }
     
        public void visit(Woman woman) {
            System.out.println("當女人戀愛時,遇事懂也裝不懂");
        }
    }

按照類圖改造一下人的接口與實現(xiàn)類
Java代碼   收藏代碼

    public interface Person {
          void accept(Visitor visitor);
    }
     
     
    public class Man implements Person{
     
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
    }
     
     
    public class Woman implements Person{
     
        public void accept(Visitor visitor) {
              visitor.visit(this);
        }
    }


這時Man與Woman變得輕盈多了,不再需要寫一大段的if,else,只需要按不同的狀態(tài),傳入不同的訪問者,執(zhí)行訪問者的方法就OK了。


為了更好地實現(xiàn)客戶類與具體元素的解耦,加入一個ObjectStructure類。有了ObjectStructure能更方便地執(zhí)行一些任何,其具體細節(jié)對于客戶端來說是透明的。

 

    import java.util.*;
     
    public class ObjectStructure {
        private List<Person> elements = new ArrayList<Person>();
     
        public void attach(Person element){
            elements.add(element);
        }
        
        public void detach(Person element){
            elements.remove(elements);
        }
        
        //遍歷各種具體元素并執(zhí)行他們的accept方法
        public void display(Visitor visitor){
            for(Person p:elements){
                p.accept(visitor);
            }
        }
    }


客戶端測試代碼:

 
Java代碼   收藏代碼

    public class Client {
          public static void main(String[] args) {
            ObjectStructure o = new ObjectStructure();  //依賴于ObjectStructure
            //實例化具體元素
            o.attach(new Man());  
            o.attach(new Woman());
            
            //當成功時不同元素的不同反映
            Visitor success = new Success();           //依賴于抽象的Visitor接口
            o.display(success);
            
            //當戀愛時的不同反映
            Visitor amativeness = new Love();          //依賴于抽象的Visitor接口
            o.display(amativeness);
        }
    }

這時客戶端只依賴于ObjectStructure類與Visitor接口,實現(xiàn)了高度的解耦,具體Visitor實現(xiàn)類的實例化與具體元素(Man,Woman)的創(chuàng)建可以通過工廠模式甚至配合properties/xml配置文件創(chuàng)建,無需客戶端關(guān)注。而Man與Gril的創(chuàng)建的代碼也可以放在其他地方,客戶端無需知道,如可以放到ObjectStructure的構(gòu)造函數(shù)中完成

 

 

        public ObjectStructure(){
            attach(new Man());  
            attach(new Woman());
        }


在實例塊{ }中完成實例化也可以。這時如果要增加一種狀態(tài)的不同操作,只需要增加一個具體訪問者,無需要修改具體元素類Man與Woman。代碼如下,

 
Java代碼   收藏代碼

    public class Fail implements Visitor{
     
        public void visit(Man man) {
            System.out.println("當男人失敗時,悶頭喝酒,誰也不用勸");
        }
     
        public void visit(Woman woman) {
            System.out.println("當女人失敗時,眼淚汪汪,誰也勸不了");
        }
    }

修改一下客戶端測試代碼就OK:

 
Java代碼   收藏代碼

 

    public class Client {
          public static void main(String[] args) {
            ObjectStructure o = new ObjectStructure();  //依賴于ObjectStructure
            //實例化具體元素
            o.attach(new Man());  
            o.attach(new Woman());
            
            //當成功時不同元素的不同反映
            Visitor success = new Success();           //依賴于抽象的Visitor接口
            o.display(success);
            
            //當戀愛時的不同反映
            Visitor amativeness = new Love();          //依賴于抽象的Visitor接口
            o.display(amativeness);
            
            //當失敗時的不同反映
            Visitor fail = new Fail();
            o.display(fail);
        }
    }


結(jié)果顯示:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
當男人失敗時,悶頭喝酒,誰也不用勸
當女人失敗時,眼淚汪汪,誰也勸不了

 

現(xiàn)在來讓我們看看訪問者模式的定義與類圖:
訪問者模式定義:表示一個作用于某個對象結(jié)構(gòu)中的各元素的操作。它使可以在不改變各元素的類的前提下定義作用于這些元素的新操作。

訪問者模式的特點:
1)優(yōu)點:使用了訪問者模式以后,對于原來的類層次增加新的操作,僅僅需要實現(xiàn)一個具體訪問者角色就可以了,而不必修改整個類層次,使得類層次結(jié)構(gòu)的代碼臃腫難以維護。而且這樣符合“開閉原則”的要求。而且每個具體的訪問者角色都對應于一個相關(guān)操作,因此如果一個操作的需求有變,那么僅僅修改一個具體訪問者角色,而不用改動整個類層次。

2)訪問者模式的雙重分派技術(shù)
(1)將具體訪問者作為參數(shù)傳遞給具體元素角色
(2)進入具體元素角色后,具體元素角色調(diào)用者作為參數(shù)的具體訪問者的visit方法,同時將自己(this)作為參數(shù)傳遞進行。具體訪問者再根據(jù)參數(shù)的不同來執(zhí)行相應的方法

3)前提:開閉原則”的遵循總是片面的。如果系統(tǒng)中的類層次發(fā)生了變化,會對訪問者模式產(chǎn)生什么樣的影響呢?你必須修改訪問者接口和每一個具體訪問者。因此4人組曾經(jīng)提出,訪問者模式適用于數(shù)據(jù)結(jié)構(gòu)相對穩(wěn)定的系統(tǒng)。

4)適用情況:訪問者模式的目的是要把處理從數(shù)據(jù)結(jié)構(gòu)分離出來。很多系統(tǒng)可以按照算法和數(shù)據(jù)結(jié)構(gòu)分開,如果這樣的系統(tǒng)有比較穩(wěn)定的數(shù)據(jù)結(jié)構(gòu),又有易于變化的算法的話,使用訪問者模式是比較合適的,因為訪問者模式似得算法操作的增加變得容易。反之,如果這樣的系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)對象易于變化,經(jīng)常要有新的數(shù)據(jù)對象增加進來,就不適合使用訪問者模式。

 



作者:chen.yu
深信服三年半工作經(jīng)驗,目前就職游戲廠商,希望能和大家交流和學習,
微信公眾號:編程入門到禿頭 或掃描下面二維碼
零基礎(chǔ)入門進階人工智能(鏈接)