23種設計模式之訪問者模式

25.2.1  訪問者模式來解決

用來解決上述問題的一個合理的解決方案,就是使用訪問者模式。那麼什麼是訪問者模式呢?

(1)訪問者模式定義


(2)應用訪問者模式來解決的思路

仔細分析上面的示例,對於客戶這個對象結構,不想改變類,又要添加新的功能,很明顯就需要一種動態的方式,在運行期間把功能動態地添加到對象結構中去。

有些朋友可能會想起裝飾模式,裝飾模式可以實現爲一個對象透明的添加功能,但裝飾模式基本上是在現有的功能的基礎之上進行功能添加,實際上是對現有功能的加強或者改造。並不是在現有功能不改動的情況下,爲對象添加新的功能。

       看來需要另外尋找新的解決方式了,可以應用訪問者模式來解決這個問題,訪問者模式實現的基本思路如下:

  • 首先定義一個接口來代表要新加入的功能,爲了通用,也就是定義一個通用的功能方法來代表新加入的功能
  • 然後在對象結構上添加一個方法,作爲通用的功能方法,也就是可以代表被添加的功能,在這個方法中傳入具體的實現新功能的對象
  • 然後在對象結構的具體實現對象裏面實現這個方法,回調傳入具體的實現新功能的對象,就相當於調用到新功能上了
  • 接下來的步驟就是提供實現新功能的對象
  • 最後再提供一個能夠循環訪問整個對象結構的類,讓這個類來提供符合客戶端業務需求的方法,來滿足客戶端調用的需要

這樣一來,只要提供實現新功能的對象給對象結構,就可以爲這些對象添加新的功能,由於在對象結構中定義的方法是通用的功能方法,所以什麼新功能都可以加入。

25.2.2  模式結構和說明

訪問者模式的結構如圖25.3所示:

 

圖25.3  訪問者模式結構示意圖

Visitor

       訪問者接口,爲所有的訪問者對象聲明一個visit方法,用來代表爲對象結構添加的功能,理論上可以代表任意的功能。

ConcreteVisitor

       具體的訪問者實現對象,實現要真正被添加到對象結構中的功能。

Element

       抽象的元素對象,對象結構的頂層接口,定義接受訪問的操作。

ConcreteElement

       具體元素對象,對象結構中具體的對象,也是被訪問的對象,通常會回調訪問者的真實功能,同時開放自身的數據供訪問者使用。

ObjectStructure

對象結構,通常包含多個被訪問的對象,它可以遍歷這多個被訪問的對象,也可以讓訪問者訪問它的元素。可以是一個複合或是一個集合,如一個列表或無序集合。

但是請注意:這個ObjectStructure並不是我們在前面講到的對象結構,前面一直講的對象結構是指的一系列對象的定義結構,是概念上的東西;而ObjectStructure可以看成是對象結構中的一系列對象的一個集合,是用來輔助客戶端訪問這一系列對象的,所以爲了不造成大家的困惑,後面提到ObjectStructure的時候,就用英文名稱來代替,不把它翻譯成中文。

25.2.3  訪問者模式示例代碼

(1)首先需要定義一個接口來代表要新加入的功能,把它稱作訪問者,訪問誰呢?當然是訪問對象結構中的對象了。既然是訪問,不能空手而去吧,這些訪問者在進行訪問的時候,就會攜帶新的功能,也就是說,訪問者攜帶着需要添加的新的功能去訪問對象結構中的對象,就相當於給對象結構中的對象添加了新的功能。示例代碼如下:

/**

 * 訪問者接口

 */

public interface Visitor {

    /**

     * 訪問元素A,相當於給元素A添加訪問者的功能

     * @param elementA 元素A的對象

     */

    public void visitConcreteElementA(ConcreteElementA elementA);

    /**

     * 訪問元素B,相當於給元素B添加訪問者的功能

     * @param elementB 元素B的對象

     */

    public void visitConcreteElementB(ConcreteElementB elementB);

}

(2)看看抽象的元素對象定義,示例代碼如下:

/**

 * 被訪問的元素的接口

 */

public abstract class Element {

    /**

     * 接受訪問者的訪問

     * @param visitor 訪問者對象

     */

    public abstract void accept(Visitor visitor);

}

(3)接下來看看元素對象的具體實現,先看元素A的實現,示例代碼如下:

/**

 * 具體元素的實現對象

 */

public class ConcreteElementA extends Element {

    public void accept(Visitor visitor) {

       //回調訪問者對象的相應方法

       visitor.visitConcreteElementA(this);

    }

    /**

     * 示例方法,表示元素已有的功能實現

     */

    public void opertionA(){

       //已有的功能實現

    }

}

再看看元素B的實現,示例代碼如下:

/**

 * 具體元素的實現對象

 */

public class ConcreteElementB extends Element {

    public void accept(Visitor visitor) {

       //回調訪問者對象的相應方法

       visitor.visitConcreteElementB(this);

    }

    /**

     * 示例方法,表示元素已有的功能實現

     */

    public void opertionB(){

       //已有的功能實現

    }

}

(4)接下來看看訪問者的具體實現,先看訪問者1的實現,示例代碼如下:

/**

 * 具體的訪問者實現

 */

public class ConcreteVisitor1 implements Visitor {

    public void visitConcreteElementA(ConcreteElementA element) {

       //把去訪問ConcreteElementA時,需要執行的功能實現在這裏

       //可能需要訪問元素已有的功能,比如:

       element.opertionA();

    }

    public void visitConcreteElementB(ConcreteElementB element) {

       //把去訪問ConcreteElementB時,需要執行的功能實現在這裏

       //可能需要訪問元素已有的功能,比如:

       element.opertionB();

    }

}

訪問者2的實現和訪問者1的示意代碼是一樣的,就不去贅述了。

(5)該來看看ObjectStructure的實現了,示例代碼如下:

/**

 * 對象結構,通常在這裏對元素對象進行遍歷,讓訪問者能訪問到所有的元素

 */

public class ObjectStructure {

    /**

     * 示意,表示對象結構,可以是一個組合結構或是集合

     */

    private Collection<Element> col = new ArrayList<Element>();

    /**

     * 示意方法,提供給客戶端操作的高層接口

     * @param visitor 客戶端需要使用的訪問者

     */

    public void handleRequest(Visitor visitor){

       //循環對象結構中的元素,接受訪問

       for(Element ele : col){

           ele.accept(visitor);

       }

    }

    /**

     * 示意方法,組建對象結構,向對象結構中添加元素。

     * 不同的對象結構有不同的構建方式

     * @param ele 加入到對象結構的元素

     */

    public void addElement(Element ele){

       this.col.add(ele);

    }

}

(6)接下來看看客戶端的示意實現,示例代碼如下:

public class Client {

    public static void main(String[] args) {

       //創建ObjectStructure

       ObjectStructure os = new ObjectStructure();

       //創建要加入對象結構的元素

       Element eleA = new ConcreteElementA();

       Element eleB = new ConcreteElementB();

       //把元素加入對象結構

       os.addElement(eleA);

       os.addElement(eleB);    

       //創建訪問者

       Visitor visitor = new ConcreteVisitor1();    

       //調用業務處理的方法

       os.handleRequest(visitor);     

    }

}

25.2.4  使用訪問者模式重寫示例

       要使用訪問者模式來重寫示例,首先就要按照訪問者模式的結構,分離出兩個類層次來,一個是對應於元素的類層次,一個是對應於訪問者的類層次。

       對於對應於元素的類層次,現在已經有了,就是客戶的對象層次。而對應於訪問者的類層次,現在還沒有,不過,按照訪問者模式的結構,應該是先定義一個訪問者接口,然後把每種業務實現成爲一個單獨的訪問者對象,也就是說應該使用一個訪問者對象來實現對客戶的偏好分析,而用另外一個訪問者對象來實現對客戶的價值分析。

       在分離好兩個類層次過後,爲了方便客戶端的訪問,定義一個ObjectStructure,其實就類似於前面示例中的客戶管理的業務對象。新的示例的結構如圖25.4所示:

 

圖25.4  使用訪問者模式的示例程序結構示意圖

       仔細查看圖25.4所示的程序結構示意圖,細心的朋友會發現,在圖上沒有出現對客戶進行價值分析的功能了。這是爲了示範“使用訪問者模式來實現示例功能過後,可以很容易的給對象結構增加新的功能”,所以先不做這個功能,等都實現好了,再來擴展這個功能。接下來還是看看代碼實現,以更好的體會訪問者模式。

(1)先來看看Customer的代碼,Customer相當於訪問者模式中的Element,它的實現跟以前相比有如下的改變:

  • 新增一個接受訪問者訪問的方法
  • 把能夠分離出去放到訪問者中實現的方法,從Customer中刪除掉,包括:客戶提出服務請求的方法、對客戶進行偏好分析的方法、對客戶進行價值分析的方法等

示例代碼如下:

public abstract class Customer {

    private String customerId;

    private String name;

    /**

     * 接受訪問者的訪問

     * @param visitor 訪問者對象

     */

    public abstract void accept(Visitor visitor);

   

 

 

 


}

(2)看看兩種客戶的實現,先看企業客戶的實現,示例代碼如下:

public class EnterpriseCustomer extends Customer{

    private String linkman;

    private String linkTelephone;

    private String registerAddress;

   

 

 

 

 


    public void accept(Visitor visitor) {

       //回調訪問者對象的相應方法

       visitor.visitEnterpriseCustomer(this);

    }

}

       再看看個人客戶的實現,示例代碼如下:

public class PersonalCustomer extends Customer{

    private String telephone;

    private int age;

   

 

 

 

    public void accept(Visitor visitor) {

       //回調訪問者對象的相應方法

       visitor.visitPersonalCustomer(this);

    }

}

(3)看看訪問者的接口定義,示例代碼如下:

/**

 * 訪問者接口

 */

public interface Visitor {

    /**

     * 訪問企業客戶,相當於給企業客戶添加訪問者的功能

     * @param ec 企業客戶的對象

     */

    public void visitEnterpriseCustomer(EnterpriseCustomer ec);

    /**

     * 訪問個人客戶,相當於給個人客戶添加訪問者的功能

     * @param pc 個人客戶的對象

     */

    public void visitPersonalCustomer(PersonalCustomer pc);

}

(4)接下來看看各個訪問者的實現,每個訪問者對象負責一類的功能處理,先看實現客戶提出服務請求的功能的訪問者,示例代碼如下:

/**

 * 具體的訪問者,實現客戶提出服務請求的功能

 */

public class ServiceRequestVisitor implements Visitor {

    public void visitEnterpriseCustomer(EnterpriseCustomer ec){

       //企業客戶提出的具體服務請求

       System.out.println(ec.getName()+"企業提出服務請求");

    }

    public void visitPersonalCustomer(PersonalCustomer pc){

       //個人客戶提出的具體服務請求

       System.out.println("客戶"+pc.getName()+"提出服務請求");

    }

}

接下來看看實現對客戶偏好分析功能的訪問者,示例代碼如下:

/**

 * 具體的訪問者,實現對客戶的偏好分析

 */

public class PredilectionAnalyzeVisitor implements Visitor {

    public void visitEnterpriseCustomer(EnterpriseCustomer ec){

       //根據過往購買的歷史、潛在購買意向

       //以及客戶所在行業的發展趨勢、客戶的發展預期等的分析

       System.out.println("現在對企業客戶"+ec.getName()

+"進行產品偏好分析");

    }

    public void visitPersonalCustomer(PersonalCustomer pc){

       System.out.println("現在對個人客戶"+pc.getName()

+"進行產品偏好分析");

    }

}

(5)接下來看看ObjectStructure的實現,示例代碼如下:

public class ObjectStructure {

    /**

     * 要操作的客戶集合

     */

    private Collection<Customer> col = new ArrayList<Customer>();

    /**

     * 提供給客戶端操作的高層接口,具體的功能由客戶端傳入的訪問者決定

     * @param visitor 客戶端需要使用的訪問者

     */

    public void handleRequest(Visitor visitor){

       //循環對象結構中的元素,接受訪問

       for(Customer cm : col){

           cm.accept(visitor);

       }

    }

    /**

     * 組建對象結構,向對象結構中添加元素。

     * 不同的對象結構有不同的構建方式

     * @param ele 加入到對象結構的元素

     */

    public void addElement(Customer ele){

       this.col.add(ele);

    }

}

(6)該來寫個客戶端測試一下了,示例代碼如下:

public class Client {

    public static void main(String[] args) {

       //創建ObjectStructure

       ObjectStructure os = new ObjectStructure();

       //準備點測試數據,創建客戶對象,並加入ObjectStructure

       Customer cm1 = new EnterpriseCustomer();

       cm1.setName("ABC集團");

       os.addElement(cm1);

      

       Customer cm2 = new EnterpriseCustomer();

       cm2.setName("CDE公司");

       os.addElement(cm2);

      

       Customer cm3 = new PersonalCustomer();

       cm3.setName("張三");

       os.addElement(cm3);

      

       //客戶提出服務請求,傳入服務請求的Visitor

       ServiceRequestVisitor srVisitor =

new ServiceRequestVisitor();

       os.handleRequest(srVisitor);

      

       //要對客戶進行偏好分析,傳入偏好分析的Visitor

       PredilectionAnalyzeVisitor paVisitor =

new PredilectionAnalyzeVisitor();

       os.handleRequest(paVisitor);

    }

}

運行結果如下:

ABC集團企業提出服務請求

CDE公司企業提出服務請求

客戶張三提出服務請求

現在對企業客戶ABC集團進行產品偏好分析

現在對企業客戶CDE公司進行產品偏好分析

現在對個人客戶張三進行產品偏好分析

(7)使用訪問者模式來重新實現了前面示例的功能,把各類相同的功能放到單獨的訪問者對象裏面,使得代碼不再雜亂,系統結構也更清晰,能方便的維護了,算是解決了前面示例的一個問題。

還有一個問題,就是看看能不能方便的增加新的功能,前面在示例的時候,故意留下了一個對客戶進行價值分析的功能沒有實現,那麼接下來就看看如何把這個功能增加到已有的系統中。在訪問者模式中要給對象結構增加新的功能,只需要把新的功能實現成爲訪問者,然後在客戶端調用的時候使用這個訪問者對象來訪問對象結構即可。

接下來看看實現對客戶價值分析功能的訪問者,示例代碼如下:

/**

 * 具體的訪問者,實現對客戶價值分析

 */

public class WorthAnalyzeVisitor implements Visitor {

    public void visitEnterpriseCustomer(EnterpriseCustomer ec){

       //根據購買的金額大小、購買的產品和服務的多少、購買的頻率等進行分析

       //企業客戶的標準會比個人客戶的高

       System.out.println("現在對企業客戶"+ec.getName()

+"進行價值分析");

    }

    public void visitPersonalCustomer(PersonalCustomer pc){

       System.out.println("現在對個人客戶"+pc.getName()

+"進行價值分析");

    }

}

使用這個功能,只要在客戶端添加如下的代碼即可,示例代碼如下:

       //要對客戶進行價值分析,傳入價值分析的Visitor

       WorthAnalyzeVisitor waVisitor = new WorthAnalyzeVisitor();

       os.handleRequest(waVisitor);

去測試看看,是否能正確地把這個功能加入到已有的程序結構中。

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