#0 系列目錄#設計模式
#1 場景問題# ##1.1 擴展客戶管理的功能## 考慮這樣一個應用:擴展客戶管理的功能。工具
既然是擴展功能,那麼確定是已經存在必定的功能了,先看看已有的功能:公司的客戶分紅兩大類,一類是企業客戶,一類是我的客戶,現有的功能很是簡單,就是能讓客戶提出服務申請
。目前的程序結構如圖25.1所示:測試
現有的實現很簡單,先看看Customer的實現,示例代碼以下:ui
/** * 各類客戶的父類 */ public abstract class Customer { /** * 客戶編號 */ private String customerId; /** * 客戶名稱 */ private String name; /** * 客戶提出服務請求的方法,示意一下 */ public abstract void serviceRequest(); }
接下來看看企業客戶的實現示例代碼以下:this
/** * 企業客戶 */ public class EnterpriseCustomer extends Customer { /** * 聯繫人 */ private String linkman; /** * 聯繫電話 */ private String linkTelephone; /** * 企業註冊地址 */ private String registerAddress; /** * 企業客戶提出服務請求的方法,示意一下 */ public void serviceRequest(){ //企業客戶提出的具體服務請求 System.out.println(this.getName()+"企業提出服務請求"); } }
再看看我的客戶的實現示例代碼以下:.net
/** * 我的客戶 */ public class PersonalCustomer extends Customer{ /** * 聯繫電話 */ private String telephone; /** * 年齡 */ private int age; /** * 企業註冊地址 */ private String registerAddress; /** * 我的客戶提出服務請求的方法,示意一下 */ public void serviceRequest(){ //我的客戶提出的具體服務請求 System.out.println("客戶"+this.getName()+"提出服務請求"); } }
從上面的實現能夠看出來,之前對客戶的管理功能是不多的,如今隨着業務的發展,須要增強對客戶管理的功能,假設如今須要增長以下的功能:翻譯
客戶對公司產品的偏好分析,針對企業客戶和我的客戶有不一樣的分析策略,主要是根據以往購買的歷史、潛在購買意向等進行分析,對於企業客戶還要添加上客戶所在行業的發展趨勢、客戶的發展預期等的分析。設計
客戶價值分析,針對企業客戶和我的客戶,有不一樣的分析方式和策略。主要是根據購買的金額大小、購買的產品和服務的多少、購買的頻率等進行分析。代理
其實除了這些功能,還有不少潛在的功能,只是如今尚未要求實現,好比:針對不一樣的客戶進行需求調查;針對不一樣的客戶進行滿意度分析;客戶消費預期分析等等。雖然如今沒有要求實現,但不排除從此有可能會要求實現。code
##1.2 不用模式的解決方案## 要實現上面要求的功能,也不是很困難,一個很基本的想法就是:既然不一樣類型的客戶操做是不一樣的,那麼在不一樣類型的客戶裏面分別實現這些功能,不就能夠了。
因爲這些功能的實現依附於不少其它功能的實現,或者是須要不少其它的業務數據,在示例裏面不太好完整的體現其功能實現,都是示意一下,所以提早說明一下。
按照上述的想法,這個時候的程序結構如圖25.2所示:
public abstract class Customer { private String customerId; private String name; public abstract void serviceRequest(); /** * 客戶對公司產品的偏好分析,示意一下 */ public abstract void predilectionAnalyze(); /** * 客戶價值分析,示意一下 */ public abstract void worthAnalyze(); }
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()+"進行價值分析"); } }
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()+"進行價值分析"); } }
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 訪問者模式來解決## 用來解決上述問題的一個合理的解決方案,就是使用訪問者模式。那麼什麼是訪問者模式呢?
仔細分析上面的示例,對於客戶這個對象結構,不想改變類,又要添加新的功能,很明顯就須要一種動態的方式,在運行期間把功能動態地添加到對象結構中去
。
有些朋友可能會想起裝飾模式,裝飾模式能夠實現爲一個對象透明的添加功能,但裝飾模式基本上是在現有的功能的基礎之上進行功能添加,其實是對現有功能的增強或者改造
。並非在現有功能不改動的狀況下,爲對象添加新的功能。
看來須要另外尋找新的解決方式了,能夠應用訪問者模式來解決這個問題,訪問者模式實現的基本思路以下:
首先定義一個接口來表明要新加入的功能,爲了通用,也就是定義一個通用的功能方法來表明新加入的功能;
而後在對象結構上添加一個方法,做爲通用的功能方法,也就是能夠表明被添加的功能,在這個方法中傳入具體的實現新功能的對象;
而後在對象結構的具體實現對象裏面實現這個方法,回調傳入具體的實現新功能的對象,就至關於調用到新功能上了;
接下來的步驟就是提供實現新功能的對象;
最後再提供一個可以循環訪問整個對象結構的類,讓這個類來提供符合客戶端業務需求的方法,來知足客戶端調用的須要;
這樣一來,只要提供實現新功能的對象給對象結構,就能夠爲這些對象添加新的功能
,因爲在對象結構中定義的方法是通用的功能方法,因此什麼新功能均可以加入。
##2.2 模式結構和說明## 訪問者模式的結構如圖25.3所示:
Visitor:訪問者接口,爲全部的訪問者對象聲明一個visit方法,
用來表明爲對象結構添加的功能,理論上能夠表明任意的功能
。ConcreteVisitor:具體的訪問者實現對象,
實現要真正被添加到對象結構中的功能
。Element:抽象的元素對象,對象結構的頂層接口,定義接受訪問的操做。
ConcreteElement:具體元素對象,對象結構中具體的對象,也是被訪問的對象,一般會回調訪問者的真實功能,同時開放自身的數據供訪問者使用。
ObjectStructure:對象結構,一般包含多個被訪問的對象,它能夠遍歷這多個被訪問的對象,也可讓訪問者訪問它的元素。能夠是一個複合或是一個集合,如一個列表或無序集合。
可是請注意:這個ObjectStructure並非咱們在前面講到的對象結構,前面一直講的對象結構是指的一系列對象的定義結構,是概念上的東西
;而ObjectStructure能夠當作是對象結構中的一系列對象的一個集合,是用來輔助客戶端訪問這一系列對象的
,因此爲了避免形成你們的困惑,後面提到ObjectStructure的時候,就用英文名稱來代替,不把它翻譯成中文。
##2.3 訪問者模式示例代碼##
首先須要定義一個接口來表明要新加入的功能,把它稱做訪問者,訪問誰呢?固然是訪問對象結構中的對象了
。既然是訪問,不能空手而去吧,這些訪問者在進行訪問的時候,就會攜帶新的功能,也就是說,訪問者攜帶着須要添加的新的功能去訪問對象結構中的對象,就至關於給對象結構中的對象添加了新的功能
。示例代碼以下:/** * 訪問者接口 */ public interface Visitor { /** * 訪問元素A,至關於給元素A添加訪問者的功能 * @param elementA 元素A的對象 */ public void visitConcreteElementA(ConcreteElementA elementA); /** * 訪問元素B,至關於給元素B添加訪問者的功能 * @param elementB 元素B的對象 */ public void visitConcreteElementB(ConcreteElementB elementB); }
/** * 被訪問的元素的接口 */ public abstract class Element { /** * 接受訪問者的訪問 * @param visitor 訪問者對象 */ public abstract void accept(Visitor visitor); }
/** * 具體元素的實現對象 */ 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(){ //已有的功能實現 } }
/** * 具體的訪問者實現 */ public class ConcreteVisitor1 implements Visitor { public void visitConcreteElementA(ConcreteElementA element) { //把去訪問ConcreteElementA時,須要執行的功能實如今這裏 //可能須要訪問元素已有的功能,好比: element.opertionA(); } public void visitConcreteElementB(ConcreteElementB element) { //把去訪問ConcreteElementB時,須要執行的功能實如今這裏 //可能須要訪問元素已有的功能,好比: element.opertionB(); } }
/** * 對象結構,一般在這裏對元素對象進行遍歷,讓訪問者能訪問到全部的元素 */ 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); } }
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,其實就相似於前面示例中的客戶管理的業務對象
。新的示例的結構如圖25.4所示:
仔細查看圖25.4所示的程序結構示意圖,細心的朋友會發現,在圖上沒有出現對客戶進行價值分析的功能了
。這是爲了示範「使用訪問者模式來實現示例功能事後,能夠很容易的給對象結構增長新的功能」,因此先不作這個功能,等都實現好了,再來擴展這個功能
。接下來仍是看看代碼實現,以更好的體會訪問者模式。
新增一個接受訪問者訪問的方法;
把可以分離出去放到訪問者中實現的方法,從Customer中刪除掉,包括:客戶提出服務請求的方法、對客戶進行偏好分析的方法、對客戶進行價值分析的方法等;
示例代碼以下:
public abstract class Customer { private String customerId; private String name; /** * 接受訪問者的訪問 * @param visitor 訪問者對象 */ public abstract void accept(Visitor visitor); }
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); } }
/** * 訪問者接口 */ public interface Visitor { /** * 訪問企業客戶,至關於給企業客戶添加訪問者的功能 * @param ec 企業客戶的對象 */ public void visitEnterpriseCustomer(EnterpriseCustomer ec); /** * 訪問我的客戶,至關於給我的客戶添加訪問者的功能 * @param pc 我的客戶的對象 */ public void visitPersonalCustomer(PersonalCustomer pc); }
/** * 具體的訪問者,實現客戶提出服務請求的功能 */ 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()+"進行產品偏好分析"); } }
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); } }
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公司進行產品偏好分析 如今對我的客戶張三進行產品偏好分析
使得代碼再也不雜亂,系統結構也更清晰,能方便的維護了
,算是解決了前面示例的一個問題。還有一個問題,就是看看能不能方便的增長新的功能
,前面在示例的時候,故意留下了一個對客戶進行價值分析的功能沒有實現,那麼接下來就看看如何把這個功能增長到已有的系統中。在訪問者模式中要給對象結構增長新的功能,只須要把新的功能實現成爲訪問者,而後在客戶端調用的時候使用這個訪問者對象來訪問對象結構便可。
接下來看看實現對客戶價值分析功能的訪問者,示例代碼以下:
/** * 具體的訪問者,實現對客戶價值分析 */ 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 模式講解# ##3.1 認識訪問者模式##
訪問者模式能給一系列對象,透明的添加新功能
。從而避免在維護期間,對這一系列對象進行修改,並且還能變相實現複用訪問者所具備的功能
。
因爲是針對一系列對象的操做,這也致使,若是隻想給一系列對象中的部分對象添加功能,就會有些麻煩;並且要始終能保證把這一系列對象都要調用到,無論是循環也好,仍是遞歸也好,總之要讓每一個對象都要被訪問到。
訪問者之因此能實現「爲一系列對象透明的添加新功能」,注意是透明的,也就是這一系列對象是不知道被添加功能的
。
重要的就是依靠通用方法
,訪問者這邊說要去訪問,就提供一個訪問的方法,如visit方法;而對象那邊說,好的,我接受你的訪問,提供一個接受訪問的方法,如accept方法。這兩個方法並不表明任何具體的功能,只是構成一個調用的通路,那麼真正的功能實如今哪裏呢?又如何調用到呢?
很簡單,就在accept方法裏面,回調visit的方法,從而回調到訪問者的具體實現上,而這個訪問者的具體實現的方法纔是要添加的新的功能
。
訪問者模式可以實如今不改變對象結構的狀況下,就能給對象結構中的類增長功能,實現這個效果所使用的核心技術就是兩次分發的技術
。
在訪問者模式中,當客戶端調用ObjectStructure的時候,會遍歷ObjectStructure中全部的元素,
調用這些元素的accept方法,讓這些元素來接受訪問,這是請求的第一次分發
;在具體的元素對象中實現accept方法的時候,
會回調訪問者的visit方法,等於請求被第二次分發了
,請求被分發給訪問者來進行處理,真正實現功能的正是訪問者的visit方法;
兩次分發技術具體的調用過程示意如圖25.5所示:
兩次分發技術使得客戶端的請求再也不被靜態的綁定在元素對象上
,這個時候真正執行什麼樣的功能同時取決於訪問者類型和元素類型,就算是同一種元素類型,只要訪問者類型不同,最終執行的功能也不會同樣,這樣一來,就能夠在元素對象不變的狀況下,經過改變訪問者的類型,來改變真正執行的功能。
兩次分發技術還有一個優勢,就是能夠在程序運行期間進行動態的功能組裝和切換,只須要在客戶端調用時,組合使用不一樣的訪問者對象實例便可。
從另外一個層面思考,Java回調技術也有點相似於兩次分發技術,客戶端調用某方法,這個方法就相似於accept方法,傳入一個接口的實現對象,這個接口的實現對象就有點像是訪問者,在方法內部,會回調這個接口的方法,就相似於調用訪問者的visit方法,最終執行的仍是接口的具體實現裏面實現的功能
。
在看上面的示例的時候,細心的朋友會發現,在企業客戶對象和我的客戶對象中實現的accept方法從表面上看是類似的,都須要回調訪問者的方法,可能就會有朋友想,爲何不把回調訪問者方法的調用語句放到父類中去,那樣不就能夠複用了嗎?
請注意,這是不能夠的,雖然看起來是類似的語句,但實際上是不一樣的,主要的玄機就在傳入的this身上
。this是表明當前的對象實例的,在企業客戶對象中傳遞的就是企業客戶對象的實例,在我的客戶對象中傳遞的就是我的客戶對象的實例,這樣在訪問者的實現中,就能夠經過這不一樣的對象實例來訪問不一樣的實例對象的數據了。
若是把這句話放到父類中,那麼傳遞的就是父類對象的實例,是沒有子對象的數據的,所以這句話不能放到父類中去。
訪問者模式的調用順序如圖25.6所示:
並非全部的訪問方法都須要實現,因爲訪問者模式默認的是訪問對象結構中的全部元素
,所以在實現某些功能的時候,若是不須要涉及到某些元素的訪問方法,這些方法能夠實現成爲空的,好比:這個訪問者只想要處理組合對象 ,那麼訪問葉子對象的方法就能夠爲空,雖然仍是須要訪問全部的元素對象。
還有一種就是有條件接受訪問
,在本身的accept方法裏面進行判斷,知足要求的接受,不知足要求的,就至關於空的訪問方法,什麼都不用作。
##3.2 操做組合對象結構## 訪問者模式一個很常見的應用,就是和組合模式結合使用,經過訪問者模式來給由組合模式構建的對象結構增長功能。
對於使用組合模式構建的組合對象結構,對外有一個統一的外觀,要想添加新的功能也不是很困難,只要在組件的接口上定義新的功能就能夠了,麻煩的是這樣一來,須要修改全部的子類。並且,每次添加一個新功能,都須要這麼痛苦一回,修改組件接口,而後修改全部的子類,這是至關糟糕的。
爲了讓組合對象結構更靈活、更容易維護和更好的擴展性,接下來把它改形成訪問者模式和組合模式組合來實現
。這樣在從此再進行功能改造的時候,就不須要再改動這個組合對象結構了。
訪問者模式和組合模式組合使用的思路:
首先把組合對象結構中的功能方法分離出來,雖然維護組合對象結構的方法也能夠分離出來,可是爲了維持組合對象結構自己,這些方法仍是放在組合對象結構裏面;而後把這些功能方法分別實現成爲訪問者對象,經過訪問者模式添加到組合的對象結構中去。
下面經過訪問者模式和組合模式組合來實現以下功能:輸出對象的名稱,在組合對象的名稱前面添加「節點:」,在葉子對象的名稱前面添加「葉子:」。
訪問者接口很是簡單,只須要定義訪問對象結構中不一樣對象的方法,示例代碼以下:
/** * 訪問組合對象結構的訪問者接口 */ public interface Visitor { /** * 訪問組合對象,至關於給組合對象添加訪問者的功能 * @param composite 組合對象 */ public void visitComposite(Composite composite); /** * 訪問葉子對象,至關於給葉子對象添加訪問者的功能 * @param leaf 葉子對象 */ public void visitLeaf(Leaf leaf); }
而後來對已有的組合對象進行改造,添加通用的功能方法,固然在參數上須要傳入訪問者。先在組件定義上添加這個方法,而後到具體的實現類裏面去實現。除了新加這個方法外,組件定義沒有其它改變,示例代碼以下:
/** * 抽象的組件對象,至關於訪問者模式中的元素對象 */ 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("對象不支持這個功能"); } }
改變了組件定義,那麼須要在組合類和葉子類上分別實現這個方法,組合類中實現的時候,一般會循環讓全部的子元素都接受訪問,這樣才能爲全部的對象都添加上新的功能
,示例代碼以下:
/** * 組合對象,能夠包含其它組合對象或者葉子對象, * 至關於訪問者模式的具體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; } }
組合對象結構已經改造好了,如今須要提供一個訪問者的實現,它會實現真正的功能,也就是要添加到對象結構中的功能。示例代碼以下:
/** * 具體的訪問者,實現:輸出對象的名稱,在組合對象的名稱前面添加"節點:", * 在葉子對象的名稱前面添加"葉子:" */ 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()); } }
訪問者是給一系列對象添加功能的,所以一個訪問者須要訪問全部的對象
,爲了方便遍歷整個對象結構,一般會定義一個專門的類出來,在這個類裏面進行元素迭代訪問,同時這個類提供客戶端訪問元素的接口。
對於這個示例,因爲在組合對象結構裏面,已經實現了對象結構的遍歷,原本是能夠不須要這個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; } }
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); } }
輸出的效果以下:
節點:服裝 節點:男裝 葉子:襯衣 葉子:夾克 節點:女裝 葉子:裙子 葉子:套裝
看看結果,是否是指望的那樣呢?
好好體會一下,想一想訪問者模式是如何實現動態的給組件添加功能的?尤爲是要想一想,實現的機制是什麼?真正實現新功能的地方在哪裏?
前面是分步的示範,你們已經體會了一番,接下來小結一下。
如同前面的示例,訪問者的方法就至關於做用於組合對象結構中各個元素的操做,是一種通用的表達,一樣的訪問者接口和一樣的方法,只要提供不一樣的訪問者具體實現,就表示不一樣的功能
。
同時在組合對象中,接受訪問的方法,也是一個通用的表達,無論你是什麼樣的功能,通通接受就行了,而後回調回去執行真正的功能。這樣一來,各元素的類就不用再修改了,只要提供不一樣的訪問者實現,而後經過這個通用表達,就結合到組合對象中來了,就至關於給全部的對象提供了新的功能
。
示例的總體結構,如圖25.7所示:
##3.3 誰負責遍歷全部元素對象## 在訪問者模式中,訪問者必需要可以訪問到對象結構中的每一個對象,由於訪問者要爲每一個對象添加功能
,爲此特別在模式中定義出一個ObjectStructure來,而後由ObjectStructure來負責遍歷訪問一系列對象中的每一個對象。
一種是元素的對象結構是經過集合來組織的,那麼直接在ObjectStructure中對集合進行迭代
,對每個元素調用accept就行了。
另外一種狀況是元素的對象結構是經過組合模式來組織的,一般能夠構成對象樹,這種狀況通常就不須要在ObjectStructure中迭代了
,而一般的作法是在組合對象的accept方法裏面,遞歸遍歷它的子元素,而後調用子元素的accept方法。
在實際開發中,有一種典型的狀況能夠不須要ObjectStructure對象,那就是隻有一個被訪問對象的時候
。只有一個被訪問對象,固然就不須要使用ObjectStructure來組合和迭代了,只要調用這個對象就行了。
事實上還有一種狀況也能夠不使用ObjectStructure,好比上面訪問的組合對象結構,從客戶端的角度看,他訪問的其實就是一個對象,所以能夠把ObjectStructure去掉,而後直接從客戶端調用元素的accept方法。
仍是經過示例來講明,先把ObjectStructure類去掉,因爲沒有了ObjectStructure,那麼客戶端調用的時候就直接調用組合對象結構的根元素的accept方法
,示例代碼以下:
public class Client { public static void main(String[] args) { //定義組件數據,組裝對象樹,跟剛纔的測試同樣,這裏就省略了 Visitor psVisitor = new PrintNameVisitor(); root.accept(psVisitor); } }
遍歷元素的方法也能夠放到訪問者當中去,固然也是須要遞歸遍歷它的子元素的
。出現這種狀況的主要緣由是:想在訪問者中實現特別複雜的遍歷,訪問者的實現依賴於對象結構的操做結果。使用訪問者模式和組合模式組合來實現了輸出名稱的功能,若是如今要實現把組合的對象結構按照樹的形式輸出,就是按照在組合模式中示例的那樣,輸出以下的樹形結構:
+服裝 +男裝 -襯衣 -夾克 +女裝 -裙子 -套裝
要實現這個功能,在組合對象結構中去遍歷子對象的方式就比較難於實現,由於要輸出這個樹形結構,須要控制每一個對象在輸出的時候,向後的退格數量,這個須要在對象結構的循環中來控制,這種功能能夠選擇在訪問者當中去遍歷對象結構。
來改造上面的示例,看看經過訪問者來遍歷元素如何實現這樣的功能。
首先在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.5 思考訪問者模式##
訪問者模式的本質:預留通路,回調實現。
仔細思考訪問者模式,它的實現主要就是經過預先定義好調用的通路,在被訪問的對象上定義accept方法,在訪問者的對象上定義visit方法;而後在調用真正發生的時候,經過兩次分發的技術,利用預先定義好的通路,回調到訪問者具體的實現上
。
明白了訪問者模式的本質,就能夠在定義一些通用功能,或者設計工具類的時候讓訪問者模式派上大用場了
。你能夠把已經實現好的一些功能,把它們做爲已有的對象結構,由於在從此可能會根據實際須要給它們增長新的功能,甚至你但願開放接口來讓其它開發人員擴展這些功能,那麼你就能夠用訪問者模式來設計,在這個對象結構上預留好通用的調用通路,在之後添加功能,或者是其它開發人員來擴展的時候,只須要提供新的訪問者實現,就可以很好的加入到系統中來了。
建議在以下狀況中,選用訪問者模式:
若是想對一個對象結構,實施一些依賴於對象結構中的具體類的操做,可使用訪問者模式。
若是想對一個對象結構中的各個元素,進行不少不一樣的並且不相關的操做,爲了不這些操做使得類變得雜亂,可使用訪問者模式,把這些操做分散到不一樣的訪問者對象中去,每一個訪問者對象實現同一類功能。
若是對象結構不多變更,可是須要常常給對象結構中的元素對象定義新的操做,可使用訪問者模式。
##3.6 相關模式##
這兩個模式能夠組合使用。
如同前面示例的那樣,經過訪問者模式給組合對象預留下擴展功能的接口,使得給組合模式的對象結構添加功能很是容易。
這兩個模式從表面看功能有些類似,都可以實如今不修改原對象結構的狀況下修改原對象的功能
。可是裝飾模式更多的是實現對已有功能增強、或者修改、或者徹底全新實現
;而訪問者模式更多的是實現給對象結構添加新的功能
。
這兩個模式能夠組合使用。
解釋器模式在構建抽象語法樹的時候,是使用組合模式來構建的
,也就是說解釋器模式解釋並執行的抽象語法樹是一個組合對象結構
,這個組合對象結構是不多變更的,可是可能常常須要爲解釋器增長新的功能
,實現對同一對象結構的不一樣解釋和執行的功能,這正好是訪問者模式的優點所在,所以這在使用解釋器模式的時候一般會組合訪問者模式來使用。