訪問者模式

1  場景問題

1.1  擴展客戶管理的功能

       考慮這樣一個應用:擴展客戶管理的功能。html

       既然是擴展功能,那麼確定是已經存在必定的功能了,先看看已有的功能:公司的客戶分紅兩大類,一類是企業客戶,一類是我的客戶,現有的功能很是簡單,就是能讓客戶提出服務申請。目前的程序結構如圖所示:java

wKiom1lm2-CghyRxAADaaYa085g776.png

現有的實現很簡單,先看看Customer的實現,示例代碼以下:設計模式

/**
 * 各類客戶的父類
 */
public abstract class Customer {
    /**
     * 客戶編號
     */
    private String customerId;
    /**
     * 客戶名稱
     */
    private String name;
 
 
 
/**
     * 客戶提出服務請求的方法,示意一下
     */
    public abstract void serviceRequest();
}

接下來看看企業客戶的實現示例代碼以下:ide

/**
 * 企業客戶
 */
public class EnterpriseCustomer extends Customer{
    /**
     * 聯繫人
     */
    private String linkman;
    /**
     * 聯繫電話
     */
    private String linkTelephone;
    /**
     * 企業註冊地址
     */
    private String registerAddress;
 
 
 

     /**
     * 企業客戶提出服務請求的方法,示意一下
     */
    public void serviceRequest(){
       //企業客戶提出的具體服務請求
       System.out.println(this.getName()+"企業提出服務請求");
    }
}

再看看我的客戶的實現示例代碼以下:測試

/**
 * 我的客戶
 */
public class PersonalCustomer extends Customer{
    /**
     * 聯繫電話
     */
    private String telephone;
    /**
     * 年齡
     */
    private int age;
    /**
     * 企業註冊地址
     */
    private String registerAddress;
 
 
 
    /**
     * 我的客戶提出服務請求的方法,示意一下
     */
    public void serviceRequest(){
       //我的客戶提出的具體服務請求
       System.out.println("客戶"+this.getName()+"提出服務請求");
    }
}

從上面的實現能夠看出來,之前對客戶的管理功能是不多的,如今隨着業務的發展,須要增強對客戶管理的功能,假設如今須要增長以下的功能:this

  • 客戶對公司產品的偏好分析,針對企業客戶和我的客戶有不一樣的分析策略,主要是根據以往購買的歷史、潛在購買意向等進行分析,對於企業客戶還要添加上客戶所在行業的發展趨勢、客戶的發展預期等的分析。翻譯

  • 客戶價值分析,針對企業客戶和我的客戶,有不一樣的分析方式和策略。主要是根據購買的金額大小、購買的產品和服務的多少、購買的頻率等進行分析。設計

其實除了這些功能,還有不少潛在的功能,只是如今尚未要求實現,好比:針對不一樣的客戶進行需求調查;針對不一樣的客戶進行滿意度分析;客戶消費預期分析等等。雖然如今沒有要求實現,但不排除從此有可能會要求實現。htm

1.2  不用模式的解決方案

       要實現上面要求的功能,也不是很困難,一個很基本的想法就是:既然不一樣類型的客戶操做是不一樣的,那麼在不一樣類型的客戶裏面分別實現這些功能,不就能夠了。對象

       因爲這些功能的實現依附於不少其它功能的實現,或者是須要不少其它的業務數據,在示例裏面不太好完整的體現其功能實現,都是示意一下,所以提早說明一下。

wKiom1lm3LPT8SM5AAEYdVBH1pc007.png

按照這個思路,把程序示意實現出來,示例以下。

(1)先看看抽象的父類,主要就是加入了兩個新的方法,示例代碼以下:

public abstract class Customer {
    private String customerId;
    private String name;
 
     public abstract void serviceRequest();
 
    /**
     * 客戶對公司產品的偏好分析,示意一下
     */
    public abstract void predilectionAnalyze();
    /**
     * 客戶價值分析,示意一下
     */
    public abstract void worthAnalyze();
}

(2)接下來看看企業客戶的示意實現,示例代碼以下:

public class EnterpriseCustomer extends Customer{
    private String linkman;
    private String linkTelephone;
    private String registerAddress;
 
     public void serviceRequest(){
       //企業客戶提出的具體服務請求
       System.out.println(this.getName()+"企業提出服務請求");
    }
 
    /**
     * 企業客戶對公司產品的偏好分析,示意一下
     */
    public void predilectionAnalyze(){
       //根據過往購買的歷史、潛在購買意向
       //以及客戶所在行業的發展趨勢、客戶的發展預期等的分析
       System.out.println("如今對企業客戶"+this.getName()
+"進行產品偏好分析");
    }
 
    /**
     * 企業客戶價值分析,示意一下
     */
    public void worthAnalyze(){
       //根據購買的金額大小、購買的產品和服務的多少、購買的頻率等進行分析
       //企業客戶的標準會比我的客戶的高
       System.out.println("如今對企業客戶"+this.getName()
+"進行價值分析");
    }
}

(3)接下來看看我的客戶的示意實現,示例代碼以下:

public class PersonalCustomer extends Customer{
    private String telephone;
    private int age;
   
    public void serviceRequest(){
       //我的客戶提出的具體服務請求
       System.out.println("客戶"+this.getName()+"提出服務請求");
    }
 
    /**
     * 我的客戶對公司產品的偏好分析,示意一下
     */
    public void predilectionAnalyze(){
       System.out.println("如今對我的客戶"+this.getName()
+"進行產品偏好分析");
    }
 
    /**
     * 我的客戶價值分析,示意一下
     */
    public void worthAnalyze(){
       System.out.println("如今對我的客戶"+this.getName()
+"進行價值分析");
    }
}

(4)如何使用上面實現的功能呢,寫個客戶端來測試一下,示例代碼以下:

public class Client {
    public static void main(String[] args) {
       //準備點測試數據
       Collection<Customer> colCustomer = preparedTestData();
       //循環對客戶進行操做
       for(Customer cm : colCustomer){
           //進行偏好分析
           cm.predilectionAnalyze();
           //進行價值分析
           cm.worthAnalyze();
       }
    }
    private static Collection<Customer> preparedTestData(){
       Collection<Customer> colCustomer =
new ArrayList<Customer>();
       //爲了測試方便,準備點數據
       Customer cm1 = new EnterpriseCustomer();
       cm1.setName("ABC集團");
       colCustomer.add(cm1);
      
       Customer cm2 = new EnterpriseCustomer();
       cm2.setName("CDE公司");
       colCustomer.add(cm2);
      
       Customer cm3 = new PersonalCustomer();
       cm3.setName("張三");
       colCustomer.add(cm3);
      
       return colCustomer;
    }
}

運行結果以下:

如今對企業客戶ABC集團進行產品偏好分析
如今對企業客戶ABC集團進行價值分析
如今對企業客戶CDE公司進行產品偏好分析
如今對企業客戶CDE公司進行價值分析
如今對我的客戶張三進行產品偏好分析
如今對我的客戶張三進行價值分析

1.3  有何問題

       以很簡單的方式,實現了要求的功能,這種實現有沒有什麼問題呢?仔細分析上面的實現,發現有兩個主要的問題:

  • 在企業客戶和我的客戶的類裏面,都分別實現了提出服務請求、進行產品偏好分析、進行客戶價值分析等功能,也就是說,這些功能的實現代碼是混雜在同一個類裏面的;並且相同的功能分散到了不一樣的類中去實現,這會致使整個系統難以理解、難以維護。

  • 更爲痛苦的是,採用這樣的實現方式,若是要給客戶擴展新的功能,好比前面提到的針對不一樣的客戶進行需求調查;針對不一樣的客戶進行滿意度分析;客戶消費預期分析等等。每次擴展,都須要改動企業客戶的類和我的客戶的類,固然也能夠經過爲它們擴展子類的方式,可是這樣可能會形成過多的對象層次。

那麼有沒有辦法,可以在不改變客戶這個對象結構中各元素類的前提下,爲這些類定義新的功能?也就是要求不改變企業客戶和我的客戶類,就能爲企業客戶和我的客戶類定義新的功能?

2  解決方案

2.1  訪問者模式來解決

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

(1)訪問者模式定義

wKioL1lm31CgVHRgAABvxtG-EIU483.png

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

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

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

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

  • 首先定義一個接口來表明要新加入的功能,爲了通用,也就是定義一個通用的功能方法來表明新加入的功能

  • 而後在對象結構上添加一個方法,做爲通用的功能方法,也就是能夠表明被添加的功能,在這個方法中傳入具體的實現新功能的對象

  • 而後在對象結構的具體實現對象裏面實現這個方法,回調傳入具體的實現新功能的對象,就至關於調用到新功能上了

  • 接下來的步驟就是提供實現新功能的對象

  • 最後再提供一個可以循環訪問整個對象結構的類,讓這個類來提供符合客戶端業務需求的方法,來知足客戶端調用的須要

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

2.2  模式結構和說明

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

wKioL1lm37PSz27fAAFqi4p--hQ955.png

Visitor

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

ConcreteVisitor

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

Element

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

ConcreteElement

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

ObjectStructure

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

可是請注意:這個ObjectStructure並非咱們在前面講到的對象結構,前面一直講的對象結構是指的一系列對象的定義結構,是概念上的東西;而ObjectStructure能夠當作是對象結構中的一系列對象的一個集合,是用來輔助客戶端訪問這一系列對象的,因此爲了避免形成你們的困惑,後面提到ObjectStructure的時候,就用英文名稱來代替,不把它翻譯成中文。

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);     
    }
}

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

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

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

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

wKiom1lm4JeB9I2ZAAHybIMeZ4k601.png

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

(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);

3.1  操做組合對象結構

       訪問者模式一個很常見的應用,就是和組合模式結合使用,經過訪問者模式來給由組合模式構建的對象結構增長功能。

對於使用組合模式構建的組合對象結構,對外有一個統一的外觀,要想添加新的功能也不是很困難,只要在組件的接口上定義新的功能就能夠了,麻煩的是這樣一來,須要修改全部的子類。並且,每次添加一個新功能,都須要這麼痛苦一回,修改組件接口,而後修改全部的子類,這是至關糟糕的。

爲了讓組合對象結構更靈活、更容易維護和更好的擴展性,接下來把它改形成訪問者模式和組合模式組合來實現。這樣在從此再進行功能改造的時候,就不須要再改動這個組合對象結構了。

訪問者模式和組合模式組合使用的思路:首先把組合對象結構中的功能方法分離出來,雖然維護組合對象結構的方法也能夠分離出來,可是爲了維持組合對象結構自己,這些方法仍是放在組合對象結構裏面;而後把這些功能方法分別實現成爲訪問者對象,經過訪問者模式添加到組合的對象結構中去。

下面經過訪問者模式和組合模式組合來實現以下功能:輸出對象的名稱,在組合對象的名稱前面添加「節點:」,在葉子對象的名稱前面添加「葉子:」。

(1)先來定義訪問者接口

訪問者接口很是簡單,只須要定義訪問對象結構中不一樣對象的方法,示例代碼以下:

/**
 * 訪問組合對象結構的訪問者接口
 */
public interface Visitor {
    /**
     * 訪問組合對象,至關於給組合對象添加訪問者的功能
     * @param composite 組合對象
     */
    public void visitComposite(Composite composite);
    /**
     * 訪問葉子對象,至關於給葉子對象添加訪問者的功能
     * @param leaf 葉子對象
     */
    public void visitLeaf(Leaf leaf);
}

(2)改造組合對象的定義

       而後來對已有的組合對象進行改造,添加通用的功能方法,固然在參數上須要傳入訪問者。先在組件定義上添加這個方法,而後到具體的實現類裏面去實現。除了新加這個方法外,組件定義沒有其它改變,示例代碼以下:

/**
 * 抽象的組件對象,至關於訪問者模式中的元素對象
 */
public abstract class Component {
    /**
     * 接受訪問者的訪問
     * @param visitor 訪問者對象
     */
    public abstract void accept(Visitor visitor);
    /**
     * 向組合對象中加入組件對象
     * @param child 被加入組合對象中的組件對象
     */
    public void addChild(Component child) {
       // 缺省實現,拋出例外,葉子對象沒這個功能,或子組件沒有實現這個功能
       throw new UnsupportedOperationException(
"對象不支持這個功能");
    }
    /**
     * 從組合對象中移出某個組件對象
     * @param child 被移出的組件對象
     */
    public void removeChild(Component child) {
       // 缺省實現,拋出例外,葉子對象沒這個功能,或子組件沒有實現這個功能
       throw new UnsupportedOperationException(
"對象不支持這個功能");
    }
    /**
     * 返回某個索引對應的組件對象
     * @param index 須要獲取的組件對象的索引,索引從0開始
     * @return 索引對應的組件對象
     */
    public Component getChildren(int index) {
       throw new UnsupportedOperationException(
"對象不支持這個功能");
    }
}

(3)實現組合對象和葉子對象

改變了組件定義,那麼須要在組合類和葉子類上分別實現這個方法,組合類中實現的時候,一般會循環讓全部的子元素都接受訪問,這樣才能爲全部的對象都添加上新的功能,示例代碼以下:

/**
 * 組合對象,能夠包含其它組合對象或者葉子對象,
 * 至關於訪問者模式的具體Element實現對象
 */
public class Composite extends Component{
    public void accept(Visitor visitor) {
       //回調訪問者對象的相應方法
       visitor.visitComposite(this);
       //循環子元素,讓子元素也接受訪問
       for(Component c : childComponents){
           //調用子對象接受訪問,變相實現遞歸
           c.accept(visitor);
       }
    }
    /**
     * 用來存儲組合對象中包含的子組件對象
     */
    private List<Component> childComponents =
new ArrayList<Component>();
    /**
     * 組合對象的名字
     */
    private String name = "";
    /**
     * 構造方法,傳入組合對象的名字
     * @param name 組合對象的名字
     */
    public Composite(String name){
       this.name = name;
    }
    public void addChild(Component child) {
       childComponents.add(child);
    }
    public String getName() {
       return name;
    }
}

葉子對象的基本實現,示例代碼以下:

/**
 * 葉子對象,至關於訪問者模式的具體Element實現對象
 */
public class Leaf extends Component{
    public void accept(Visitor visitor) {
       //回調訪問者對象的相應方法
       visitor.visitLeaf(this);
    }
    /**
     * 葉子對象的名字
     */
    private String name = "";
    /**
     * 構造方法,傳入葉子對象的名字
     * @param name 葉子對象的名字
     */
    public Leaf(String name){
       this.name = name;
    }
    public String getName() {
       return name;
    }
}

(4)實現一個訪問者

       組合對象結構已經改造好了,如今須要提供一個訪問者的實現,它會實現真正的功能,也就是要添加到對象結構中的功能。示例代碼以下:

/**
 * 具體的訪問者,實現:輸出對象的名稱,在組合對象的名稱前面添加"節點:",
 * 在葉子對象的名稱前面添加"葉子:"
*/
public class PrintNameVisitor implements Visitor {
    public void visitComposite(Composite composite) {
       //訪問到組合對象的數據
       System.out.println("節點:"+composite.getName());
    }
    public void visitLeaf(Leaf leaf) {
       //訪問到葉子對象的數據     
       System.out.println("葉子:"+leaf.getName());
    }
}

(5)訪問全部元素對象的對象——ObjectStructure

       訪問者是給一系列對象添加功能的,所以一個訪問者須要訪問全部的對象,爲了方便遍歷整個對象結構,一般會定義一個專門的類出來,在這個類裏面進行元素迭代訪問,同時這個類提供客戶端訪問元素的接口。

       對於這個示例,因爲在組合對象結構裏面,已經實現了對象結構的遍歷,原本是能夠不須要這個ObjectStructure的,可是爲了更清晰的展現訪問者模式的結構,也爲了從此的擴展或實現方便,仍是定義一個ObjectStructure。示例代碼以下:

/**
 * 對象結構,一般在這裏對元素對象進行遍歷,讓訪問者能訪問到全部的元素
 */
public class ObjectStructure {
    /**
     * 表示對象結構,能夠是一個組合結構
     */
    private Component root = null;
    /**
     * 提供給客戶端操做的高層接口
     * @param visitor 客戶端須要使用的訪問者
     */
    public void handleRequest(Visitor visitor){
       //讓組合對象結構中的根元素,接受訪問
       //在組合對象結構中已經實現了元素的遍歷
       if(root!=null){
           root.accept(visitor);
       }
    }
    /**
     * 傳入組合對象結構
     * @param ele 組合對象結構
     */
    public void setRoot(Component ele){
       this.root = ele;
    }
}

(6)寫個客戶端,來看看如何經過訪問者去爲對象結構添加新的功能,示例代碼以下:

public class Client {
    public static void main(String[] args) {
       //定義全部的組合對象
       Component root = new Composite("服裝");
       Component c1 = new Composite("男裝");
       Component c2 = new Composite("女裝");
       //定義全部的葉子對象
       Component leaf1 = new Leaf("襯衣");
       Component leaf2 = new Leaf("夾克");
       Component leaf3 = new Leaf("裙子");
       Component leaf4 = new Leaf("套裝");
       //按照樹的結構來組合組合對象和葉子對象
       root.addChild(c1);
       root.addChild(c2);
      
       c1.addChild(leaf1);
       c1.addChild(leaf2);
      
       c2.addChild(leaf3);
       c2.addChild(leaf4);
      
       //建立ObjectStructure
       ObjectStructure os = new ObjectStructure();
       os.setRoot(root);
       
       //調用ObjectStructure來處理請求功能
       Visitor psVisitor = new PrintNameVisitor(); 
       os.handleRequest(psVisitor);
    }
}

輸出的效果以下:

節點:服裝
節點:男裝
葉子:襯衣
葉子:夾克
節點:女裝
葉子:裙子
葉子:套裝

(7)如今的程序結構

       前面是分步的示範,你們已經體會了一番,接下來小結一下。

如同前面的示例,訪問者的方法就至關於做用於組合對象結構中各個元素的操做,是一種通用的表達,一樣的訪問者接口和一樣的方法,只要提供不一樣的訪問者具體實現,就表示不一樣的功能。

同時在組合對象中,接受訪問的方法,也是一個通用的表達,無論你是什麼樣的功能,通通接受就行了,而後回調回去執行真正的功能。這樣一來,各元素的類就不用再修改了,只要提供不一樣的訪問者實現,而後經過這個通用表達,就結合到組合對象中來了,就至關於給全部的對象提供了新的功能。

示例的總體結構,如圖所示:

wKiom1lm5C-Rm-BbAAH3Gpj1Hy4751.png

3.2  誰負責遍歷全部元素對象

在訪問者模式中,訪問者必需要可以訪問到對象結構中的每一個對象,由於訪問者要爲每一個對象添加功能,爲此特別在模式中定義出一個ObjectStructure來,而後由ObjectStructure來負責遍歷訪問一系列對象中的每一個對象。

(1)在ObjectStructure迭代全部的元素時,又分紅兩種狀況。

  • 一種是元素的對象結構是經過集合來組織的,那麼直接在ObjectStructure中對集合進行迭代,對每個元素調用accept就行了。如同前面25.2.4的示例所採用的方式。

  • 另外一種狀況是元素的對象結構是經過組合模式來組織的,一般能夠構成對象樹,這種狀況通常就不須要在ObjectStructure中迭代了,而一般的作法是在組合對象的accept方法裏面,遞歸遍歷它的子元素,而後調用子元素的accept方法,如同前面25.3.2的示例中Composite的實現,在accept方法裏面進行遞歸調用子對象的操做。

(2)不須要ObjectStructure的時候

在實際開發中,有一種典型的狀況能夠不須要ObjectStructure對象,那就是隻有一個被訪問對象的時候。只有一個被訪問對象,固然就不須要使用ObjectStructure來組合和迭代了,只要調用這個對象就行了。

事實上還有一種狀況也能夠不使用ObjectStructure,好比上面訪問的組合對象結構,從客戶端的角度看,他訪問的其實就是一個對象,所以能夠把ObjectStructure去掉,而後直接從客戶端調用元素的accept方法。

仍是經過示例來講明,先把ObjectStructure類去掉,因爲沒有了ObjectStructure,那麼客戶端調用的時候就直接調用組合對象結構的根元素的accept方法,示例代碼以下:

public class Client {
    public static void main(String[] args) {
       //定義組件數據,組裝對象樹,跟剛纔的測試同樣,這裏就省略了  
 
       Visitor psVisitor = new PrintNameVisitor();
       root.accept(psVisitor); 
    }
}

(3)有些時候,遍歷元素的方法也能夠放到訪問者當中去,固然也是須要遞歸遍歷它的子元素的。出現這種狀況的主要緣由是:想在訪問者中實現特別複雜的遍歷,訪問者的實現依賴於對象結構的操做結果。

好比3.2的示例,使用訪問者模式和組合模式組合來實現了輸出名稱的功能,若是如今要實現把組合的對象結構按照樹的形式輸出,就是按照在組合模式中示例的那樣,輸出以下的樹形結構:

+服裝
 +男裝
  -襯衣
  -夾克
 +女裝
  -裙子
  -套裝

要實現這個功能,在組合對象結構中去遍歷子對象的方式就比較難於實現,由於要輸出這個樹形結構,須要控制每一個對象在輸出的時候,向後的退格數量,這個須要在對象結構的循環中來控制,這種功能能夠選擇在訪問者當中去遍歷對象結構。

來改造上面的示例,看看經過訪問者來遍歷元素如何實現這樣的功能。

首先在Composite的accept實現中去除掉遞歸調用子對象的代碼,同時添加一個讓訪問者訪問到其所包含的子對象的方法,示例代碼以下:

public class Composite extends Component{
    //其它相同部分就省略了,只看變化的方法
     public void accept(Visitor visitor) {
       //回調訪問者對象的相應方法
       visitor.visitComposite(this);
     
   //  for(Component c : childComponents){ 
   //   調用子對象接受訪問,變相實現遞歸 
   //       c.accept(visitor); 
   //  } 
    }
public List<Component> getChildComponents() {
       return childComponents;
    }
}

而後新實現一個訪問者對象,在相應的visit實現裏面,添加遞歸迭代全部子對象,示例代碼以下:

/**
 * 具體的訪問者,實現:輸出組合對象自身的結構
*/
public class PrintStructVisitor implements Visitor {
    /**
     * 用來累計記錄對象須要向後退的格
     */
    private String preStr = "";
    public void visitComposite(Composite composite) {
       //先把本身輸出去
       System.out.println(preStr+"+"+composite.getName());
       //若是還包含有子組件,那麼就輸出這些子組件對象
       if(composite.getChildComponents()!=null){
           //而後添加一個空格,表示向後縮進一個空格
           preStr+=" ";     
           //輸出當前對象的子對象了
           for(Component c : composite.getChildComponents()){
              //遞歸輸出每一個子對象
              c.accept(this);
           }
           //把循環子對象所多加入的一個退格給去掉
           preStr = preStr.substring(0,preStr.length()-1);
       }
    }
    public void visitLeaf(Leaf leaf) {
       //訪問到葉子對象的數據     
       System.out.println(preStr+"-"+leaf.getName());
    }
}

寫個客戶端來測試一下看看,是否能實現要求的功能。示例代碼以下:

public class Client {
    public static void main(String[] args) {
       //定義全部的組合對象過程跟上一個client是同樣的,這裏省略了   
       //調用根元素的方法來接受請求功能
       Visitor psVisitor = new PrintStructVisitor();
       root.accept(psVisitor);
    }
}

3.4  訪問者模式優缺點

 好的擴展性
    可以在不修改對象結構中的元素的狀況下,給對象結構中的元素添加新的功能

 好的複用性
    能夠經過訪問者來定義整個對象結構通用的功能,從而提升複用程度

 分離無關行爲
    能夠經過訪問者來分離無關的行爲,把相關的行爲封裝在一塊兒,構成一個訪問者,這樣每個訪問者的功能都比較單一。

 對象結構變化很困難
    不適用於對象結構中的類常常變化的狀況,由於對象結構發生了改變,訪問者的接口和訪問者的實現都要發生相應的改變,代價過高

 破壞封裝
    訪問者模式一般須要對象結構開放內部數據給訪問者和ObjectStructrue,這破壞了對象的封裝性

3.6  相關模式

 訪問者模式和組合模式
    這兩個模式能夠組合使用。
    如同前面示例的那樣,經過訪問者模式給組合對象預留下擴展功能的接口,使得給組合模式的對象結構添加功能很是容易。

 訪問者模式和裝飾模式
    這兩個模式從表面看功能有些類似,都可以實如今不修改原對象結構的狀況下修改原對象的功能。可是裝飾模式更多的是實現對已有功能增強、或者修改、或者徹底全新實現;而訪問者模式更多的是實現給對象結構添加新的功能。

訪問者模式和解釋器模式
    這兩個模式能夠組合使用。
    解釋器模式在構建抽象語法樹的時候,是使用組合模式來構建的,也就是說解釋器模式解釋並執行的抽象語法樹是一個組合對象結構,這個組合對象結構是不多變更的,可是可能常常須要爲解釋器增長新的功能,實現對同一對象結構的不一樣解釋和執行的功能,這正好是訪問者模式的優點所在,所以這在使用解釋器模式的時候一般會組合訪問者模式來使用。


轉載至:http://sishuok.com/forum/blogPost/list/5881.html

   cc老師的設計模式是我目前看過最詳細最有實踐的教程。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息