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); |
去測試看看,是否能正確地把這個功能加入到已有的程序結構中。