1 場(chǎng)景問(wèn)題#
1.1 擴(kuò)展客戶管理的功能##
考慮這樣一個(gè)應(yīng)用:擴(kuò)展客戶管理的功能。
既然是擴(kuò)展功能,那么肯定是已經(jīng)存在一定的功能了,先看看已有的功能:公司的客戶分成兩大類,一類是企業(yè)客戶,一類是個(gè)人客戶系冗,現(xiàn)有的功能非常簡(jiǎn)單,就是能讓客戶提出服務(wù)申請(qǐng)
薪鹦。目前的程序結(jié)構(gòu)如圖所示:
現(xiàn)有的實(shí)現(xiàn)很簡(jiǎn)單掌敬,先看看Customer的實(shí)現(xiàn),示例代碼如下:
/**
* 各種客戶的父類
*/
public abstract class Customer {
/**
* 客戶編號(hào)
*/
private String customerId;
/**
* 客戶名稱
*/
private String name;
/**
* 客戶提出服務(wù)請(qǐng)求的方法池磁,示意一下
*/
public abstract void serviceRequest();
}
接下來(lái)看看企業(yè)客戶的實(shí)現(xiàn)示例代碼如下:
/**
* 企業(yè)客戶
*/
public class EnterpriseCustomer extends Customer {
/**
* 聯(lián)系人
*/
private String linkman;
/**
* 聯(lián)系電話
*/
private String linkTelephone;
/**
* 企業(yè)注冊(cè)地址
*/
private String registerAddress;
/**
* 企業(yè)客戶提出服務(wù)請(qǐng)求的方法奔害,示意一下
*/
public void serviceRequest(){
//企業(yè)客戶提出的具體服務(wù)請(qǐng)求
System.out.println(this.getName()+"企業(yè)提出服務(wù)請(qǐng)求");
}
}
再看看個(gè)人客戶的實(shí)現(xiàn)示例代碼如下:
/**
* 個(gè)人客戶
*/
public class PersonalCustomer extends Customer{
/**
* 聯(lián)系電話
*/
private String telephone;
/**
* 年齡
*/
private int age;
/**
* 企業(yè)注冊(cè)地址
*/
private String registerAddress;
/**
* 個(gè)人客戶提出服務(wù)請(qǐng)求的方法,示意一下
*/
public void serviceRequest(){
//個(gè)人客戶提出的具體服務(wù)請(qǐng)求
System.out.println("客戶"+this.getName()+"提出服務(wù)請(qǐng)求");
}
}
從上面的實(shí)現(xiàn)可以看出來(lái)地熄,以前對(duì)客戶的管理功能是很少的华临,現(xiàn)在隨著業(yè)務(wù)的發(fā)展,需要加強(qiáng)對(duì)客戶管理的功能端考,假設(shè)現(xiàn)在需要增加如下的功能:
客戶對(duì)公司產(chǎn)品的偏好分析雅潭,針對(duì)企業(yè)客戶和個(gè)人客戶有不同的分析策略,主要是根據(jù)以往購(gòu)買(mǎi)的歷史却特、潛在購(gòu)買(mǎi)意向等進(jìn)行分析扶供,對(duì)于企業(yè)客戶還要添加上客戶所在行業(yè)的發(fā)展趨勢(shì)、客戶的發(fā)展預(yù)期等的分析核偿。
客戶價(jià)值分析诚欠,針對(duì)企業(yè)客戶和個(gè)人客戶顽染,有不同的分析方式和策略漾岳。主要是根據(jù)購(gòu)買(mǎi)的金額大小、購(gòu)買(mǎi)的產(chǎn)品和服務(wù)的多少粉寞、購(gòu)買(mǎi)的頻率等進(jìn)行分析尼荆。
其實(shí)除了這些功能,還有很多潛在的功能唧垦,只是現(xiàn)在還沒(méi)有要求實(shí)現(xiàn)捅儒,比如:針對(duì)不同的客戶進(jìn)行需求調(diào)查;針對(duì)不同的客戶進(jìn)行滿意度分析;客戶消費(fèi)預(yù)期分析等等巧还。雖然現(xiàn)在沒(méi)有要求實(shí)現(xiàn)鞭莽,但不排除今后有可能會(huì)要求實(shí)現(xiàn)。
1.2 不用模式的解決方案##
要實(shí)現(xiàn)上面要求的功能麸祷,也不是很困難澎怒,一個(gè)很基本的想法就是:既然不同類型的客戶操作是不同的,那么在不同類型的客戶里面分別實(shí)現(xiàn)這些功能阶牍,不就可以了喷面。
由于這些功能的實(shí)現(xiàn)依附于很多其它功能的實(shí)現(xiàn),或者是需要很多其它的業(yè)務(wù)數(shù)據(jù)走孽,在示例里面不太好完整的體現(xiàn)其功能實(shí)現(xiàn)惧辈,都是示意一下,因此提前說(shuō)明一下磕瓷。
按照上述的想法盒齿,這個(gè)時(shí)候的程序結(jié)構(gòu)如圖所示:
- 先看看抽象的父類,主要就是加入了兩個(gè)新的方法困食,示例代碼如下:
public abstract class Customer {
private String customerId;
private String name;
public abstract void serviceRequest();
/**
* 客戶對(duì)公司產(chǎn)品的偏好分析县昂,示意一下
*/
public abstract void predilectionAnalyze();
/**
* 客戶價(jià)值分析,示意一下
*/
public abstract void worthAnalyze();
}
- 接下來(lái)看看企業(yè)客戶的示意實(shí)現(xiàn)陷舅,示例代碼如下:
public class EnterpriseCustomer extends Customer {
private String linkman;
private String linkTelephone;
private String registerAddress;
public void serviceRequest(){
//企業(yè)客戶提出的具體服務(wù)請(qǐng)求
System.out.println(this.getName()+"企業(yè)提出服務(wù)請(qǐng)求");
}
/**
* 企業(yè)客戶對(duì)公司產(chǎn)品的偏好分析倒彰,示意一下
*/
public void predilectionAnalyze(){
//根據(jù)過(guò)往購(gòu)買(mǎi)的歷史、潛在購(gòu)買(mǎi)意向
//以及客戶所在行業(yè)的發(fā)展趨勢(shì)莱睁、客戶的發(fā)展預(yù)期等的分析
System.out.println("現(xiàn)在對(duì)企業(yè)客戶"+this.getName()+"進(jìn)行產(chǎn)品偏好分析");
}
/**
* 企業(yè)客戶價(jià)值分析,示意一下
*/
public void worthAnalyze(){
//根據(jù)購(gòu)買(mǎi)的金額大小创淡、購(gòu)買(mǎi)的產(chǎn)品和服務(wù)的多少琳彩、購(gòu)買(mǎi)的頻率等進(jìn)行分析
//企業(yè)客戶的標(biāo)準(zhǔn)會(huì)比個(gè)人客戶的高
System.out.println("現(xiàn)在對(duì)企業(yè)客戶"+this.getName()+"進(jìn)行價(jià)值分析");
}
}
- 接下來(lái)看看個(gè)人客戶的示意實(shí)現(xiàn)露乏,示例代碼如下:
public class PersonalCustomer extends Customer{
private String telephone;
private int age;
public void serviceRequest(){
//個(gè)人客戶提出的具體服務(wù)請(qǐng)求
System.out.println("客戶"+this.getName()+"提出服務(wù)請(qǐng)求");
}
/**
* 個(gè)人客戶對(duì)公司產(chǎn)品的偏好分析涂邀,示意一下
*/
public void predilectionAnalyze(){
System.out.println("現(xiàn)在對(duì)個(gè)人客戶"+this.getName()+"進(jìn)行產(chǎn)品偏好分析");
}
/**
* 個(gè)人客戶價(jià)值分析瘟仿,示意一下
*/
public void worthAnalyze(){
System.out.println("現(xiàn)在對(duì)個(gè)人客戶"+this.getName()+"進(jìn)行價(jià)值分析");
}
}
- 如何使用上面實(shí)現(xiàn)的功能呢,寫(xiě)個(gè)客戶端來(lái)測(cè)試一下比勉,示例代碼如下:
public class Client {
public static void main(String[] args) {
//準(zhǔn)備點(diǎn)測(cè)試數(shù)據(jù)
Collection<Customer> colCustomer = preparedTestData();
//循環(huán)對(duì)客戶進(jìn)行操作
for(Customer cm : colCustomer){
//進(jìn)行偏好分析
cm.predilectionAnalyze();
//進(jìn)行價(jià)值分析
cm.worthAnalyze();
}
}
private static Collection<Customer> preparedTestData(){
Collection<Customer> colCustomer = new ArrayList<Customer>();
//為了測(cè)試方便劳较,準(zhǔn)備點(diǎn)數(shù)據(jù)
Customer cm1 = new EnterpriseCustomer();
cm1.setName("ABC集團(tuán)");
colCustomer.add(cm1);
Customer cm2 = new EnterpriseCustomer();
cm2.setName("CDE公司");
colCustomer.add(cm2);
Customer cm3 = new PersonalCustomer();
cm3.setName("張三");
colCustomer.add(cm3);
return colCustomer;
}
}
運(yùn)行結(jié)果如下:
現(xiàn)在對(duì)企業(yè)客戶ABC集團(tuán)進(jìn)行產(chǎn)品偏好分析
現(xiàn)在對(duì)企業(yè)客戶ABC集團(tuán)進(jìn)行價(jià)值分析
現(xiàn)在對(duì)企業(yè)客戶CDE公司進(jìn)行產(chǎn)品偏好分析
現(xiàn)在對(duì)企業(yè)客戶CDE公司進(jìn)行價(jià)值分析
現(xiàn)在對(duì)個(gè)人客戶張三進(jìn)行產(chǎn)品偏好分析
現(xiàn)在對(duì)個(gè)人客戶張三進(jìn)行價(jià)值分析
1.3 有何問(wèn)題##
以很簡(jiǎn)單的方式驹止,實(shí)現(xiàn)了要求的功能,這種實(shí)現(xiàn)有沒(méi)有什么問(wèn)題呢观蜗?仔細(xì)分析上面的實(shí)現(xiàn)臊恋,發(fā)現(xiàn)有兩個(gè)主要的問(wèn)題:
在企業(yè)客戶和個(gè)人客戶的類里面,都分別實(shí)現(xiàn)了提出服務(wù)請(qǐng)求墓捻、進(jìn)行產(chǎn)品偏好分析捞镰、進(jìn)行客戶價(jià)值分析等功能,也就是說(shuō)毙替,
這些功能的實(shí)現(xiàn)代碼是混雜在同一個(gè)類里面的
岸售;而且相同的功能分散到了不同的類中去實(shí)現(xiàn),這會(huì)導(dǎo)致整個(gè)系統(tǒng)難以理解厂画、難以維護(hù)
凸丸。更為痛苦的是屎慢,采用這樣的實(shí)現(xiàn)方式腻惠,如果要給客戶擴(kuò)展新的功能,比如前面提到的針對(duì)不同的客戶進(jìn)行需求調(diào)查欣喧;針對(duì)不同的客戶進(jìn)行滿意度分析;客戶消費(fèi)預(yù)期分析等等驯鳖。
每次擴(kuò)展,都需要改動(dòng)企業(yè)客戶的類和個(gè)人客戶的類
摔握,當(dāng)然也可以通過(guò)為它們擴(kuò)展子類的方式氨淌,但是這樣可能會(huì)造成過(guò)多的對(duì)象層次
盛正。
那么有沒(méi)有辦法,能夠在不改變客戶這個(gè)對(duì)象結(jié)構(gòu)中各元素類的前提下续崖,為這些類定義新的功能?也就是要求不改變企業(yè)客戶和個(gè)人客戶類像吻,就能為企業(yè)客戶和個(gè)人客戶類定義新的功能?
2 解決方案#
2.1 訪問(wèn)者模式來(lái)解決##
用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案惭每,就是使用訪問(wèn)者模式洪鸭。那么什么是訪問(wèn)者模式呢?
- 訪問(wèn)者模式定義
- 應(yīng)用訪問(wèn)者模式來(lái)解決的思路
仔細(xì)分析上面的示例,對(duì)于客戶這個(gè)對(duì)象結(jié)構(gòu)俱济,不想改變類蛛碌,又要添加新的功能希太,很明顯就需要一種動(dòng)態(tài)的方式,在運(yùn)行期間把功能動(dòng)態(tài)地添加到對(duì)象結(jié)構(gòu)中去堕澄。
有些朋友可能會(huì)想起裝飾模式蛙紫,裝飾模式可以實(shí)現(xiàn)為一個(gè)對(duì)象透明的添加功能,但裝飾模式基本上是在現(xiàn)有的功能的基礎(chǔ)之上進(jìn)行功能添加裁蚁,實(shí)際上是對(duì)現(xiàn)有功能的加強(qiáng)或者改造。并不是在現(xiàn)有功能不改動(dòng)的情況下室谚,為對(duì)象添加新的功能秒赤。
看來(lái)需要另外尋找新的解決方式了,可以應(yīng)用訪問(wèn)者模式來(lái)解決這個(gè)問(wèn)題潮售,訪問(wèn)者模式實(shí)現(xiàn)的基本思路如下:
首先定義一個(gè)接口來(lái)代表要新加入的功能,為了通用肮帐,也就是定義一個(gè)通用的功能方法來(lái)代表新加入的功能托修;
然后在對(duì)象結(jié)構(gòu)上添加一個(gè)方法诀黍,作為通用的功能方法,也就是可以代表被添加的功能婆誓,在這個(gè)方法中傳入具體的實(shí)現(xiàn)新功能的對(duì)象;
然后在對(duì)象結(jié)構(gòu)的具體實(shí)現(xiàn)對(duì)象里面實(shí)現(xiàn)這個(gè)方法文留,回調(diào)傳入具體的實(shí)現(xiàn)新功能的對(duì)象,就相當(dāng)于調(diào)用到新功能上了森书;
接下來(lái)的步驟就是提供實(shí)現(xiàn)新功能的對(duì)象;
最后再提供一個(gè)能夠循環(huán)訪問(wèn)整個(gè)對(duì)象結(jié)構(gòu)的類猖毫,讓這個(gè)類來(lái)提供符合客戶端業(yè)務(wù)需求的方法鄙麦,來(lái)滿足客戶端調(diào)用的需要;
這樣一來(lái)骂因,只要提供實(shí)現(xiàn)新功能的對(duì)象給對(duì)象結(jié)構(gòu)乘盼,就可以為這些對(duì)象添加新的功能,由于在對(duì)象結(jié)構(gòu)中定義的方法是通用的功能方法粹胯,所以什么新功能都可以加入风纠。
2.2 模式結(jié)構(gòu)和說(shuō)明##
訪問(wèn)者模式的結(jié)構(gòu)如圖所示:
Visitor:訪問(wèn)者接口潜索,為所有的訪問(wèn)者對(duì)象聲明一個(gè)visit方法誊抛,
用來(lái)代表為對(duì)象結(jié)構(gòu)添加的功能芍锚,理論上可以代表任意的功能
。ConcreteVisitor:具體的訪問(wèn)者實(shí)現(xiàn)對(duì)象逃魄,
實(shí)現(xiàn)要真正被添加到對(duì)象結(jié)構(gòu)中的功能
。Element:抽象的元素對(duì)象癌瘾,對(duì)象結(jié)構(gòu)的頂層接口,定義接受訪問(wèn)的操作冠句。
ConcreteElement:具體元素對(duì)象懦底,對(duì)象結(jié)構(gòu)中具體的對(duì)象,也是被訪問(wèn)的對(duì)象拱层,通常會(huì)回調(diào)訪問(wèn)者的真實(shí)功能,同時(shí)開(kāi)放自身的數(shù)據(jù)供訪問(wèn)者使用。
ObjectStructure:對(duì)象結(jié)構(gòu)氧卧,通常包含多個(gè)被訪問(wèn)的對(duì)象沙绝,它可以遍歷這多個(gè)被訪問(wèn)的對(duì)象,也可以讓訪問(wèn)者訪問(wèn)它的元素粗悯。可以是一個(gè)復(fù)合或是一個(gè)集合衫哥,如一個(gè)列表或無(wú)序集合撤逢。
但是請(qǐng)注意:這個(gè)ObjectStructure并不是我們?cè)谇懊嬷v到的對(duì)象結(jié)構(gòu),前面一直講的對(duì)象結(jié)構(gòu)是指的一系列對(duì)象的定義結(jié)構(gòu)跷究,是概念上的東西
;而ObjectStructure可以看成是對(duì)象結(jié)構(gòu)中的一系列對(duì)象的一個(gè)集合柴我,是用來(lái)輔助客戶端訪問(wèn)這一系列對(duì)象的
,所以為了不造成大家的困惑界睁,后面提到ObjectStructure的時(shí)候翻斟,就用英文名稱來(lái)代替,不把它翻譯成中文债热。
2.3 訪問(wèn)者模式示例代碼##
首先需要定義一個(gè)接口來(lái)代表要新加入的功能,把它稱作訪問(wèn)者舌剂,訪問(wèn)誰(shuí)呢?當(dāng)然是訪問(wèn)對(duì)象結(jié)構(gòu)中的對(duì)象了
避消。既然是訪問(wèn)恕沫,不能空手而去吧,這些訪問(wèn)者在進(jìn)行訪問(wèn)的時(shí)候迄委,就會(huì)攜帶新的功能,也就是說(shuō)信轿,訪問(wèn)者攜帶著需要添加的新的功能去訪問(wèn)對(duì)象結(jié)構(gòu)中的對(duì)象愧旦,就相當(dāng)于給對(duì)象結(jié)構(gòu)中的對(duì)象添加了新的功能
笤虫。示例代碼如下:
/**
* 訪問(wèn)者接口
*/
public interface Visitor {
/**
* 訪問(wèn)元素A酬凳,相當(dāng)于給元素A添加訪問(wèn)者的功能
* @param elementA 元素A的對(duì)象
*/
public void visitConcreteElementA(ConcreteElementA elementA);
/**
* 訪問(wèn)元素B,相當(dāng)于給元素B添加訪問(wèn)者的功能
* @param elementB 元素B的對(duì)象
*/
public void visitConcreteElementB(ConcreteElementB elementB);
}
- 看看抽象的元素對(duì)象定義翎苫,示例代碼如下:
/**
* 被訪問(wèn)的元素的接口
*/
public abstract class Element {
/**
* 接受訪問(wèn)者的訪問(wèn)
* @param visitor 訪問(wèn)者對(duì)象
*/
public abstract void accept(Visitor visitor);
}
- 接下來(lái)看看元素對(duì)象的具體實(shí)現(xiàn),先看元素A的實(shí)現(xiàn)呐粘,示例代碼如下:
/**
* 具體元素的實(shí)現(xiàn)對(duì)象
*/
public class ConcreteElementA extends Element {
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitConcreteElementA(this);
}
/**
* 示例方法,表示元素已有的功能實(shí)現(xiàn)
*/
public void opertionA(){
//已有的功能實(shí)現(xiàn)
}
}
再看看元素B的實(shí)現(xiàn)降盹,示例代碼如下:
/**
* 具體元素的實(shí)現(xiàn)對(duì)象
*/
public class ConcreteElementB extends Element {
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitConcreteElementB(this);
}
/**
* 示例方法,表示元素已有的功能實(shí)現(xiàn)
*/
public void opertionB(){
//已有的功能實(shí)現(xiàn)
}
}
- 接下來(lái)看看訪問(wèn)者的具體實(shí)現(xiàn)涡戳,先看訪問(wèn)者1的實(shí)現(xiàn),示例代碼如下:
/**
* 具體的訪問(wèn)者實(shí)現(xiàn)
*/
public class ConcreteVisitor1 implements Visitor {
public void visitConcreteElementA(ConcreteElementA element) {
//把去訪問(wèn)ConcreteElementA時(shí)恍涂,需要執(zhí)行的功能實(shí)現(xiàn)在這里
//可能需要訪問(wèn)元素已有的功能,比如:
element.opertionA();
}
public void visitConcreteElementB(ConcreteElementB element) {
//把去訪問(wèn)ConcreteElementB時(shí)炒瘸,需要執(zhí)行的功能實(shí)現(xiàn)在這里
//可能需要訪問(wèn)元素已有的功能,比如:
element.opertionB();
}
}
- 該來(lái)看看ObjectStructure的實(shí)現(xiàn)了隘截,示例代碼如下:
/**
* 對(duì)象結(jié)構(gòu),通常在這里對(duì)元素對(duì)象進(jìn)行遍歷,讓訪問(wèn)者能訪問(wèn)到所有的元素
*/
public class ObjectStructure {
/**
* 示意雕擂,表示對(duì)象結(jié)構(gòu)谤逼,可以是一個(gè)組合結(jié)構(gòu)或是集合
*/
private Collection<Element> col = new ArrayList<Element>();
/**
* 示意方法流部,提供給客戶端操作的高層接口
* @param visitor 客戶端需要使用的訪問(wèn)者
*/
public void handleRequest(Visitor visitor){
//循環(huán)對(duì)象結(jié)構(gòu)中的元素,接受訪問(wèn)
for(Element ele : col){
ele.accept(visitor);
}
}
/**
* 示意方法果漾,組建對(duì)象結(jié)構(gòu),向?qū)ο蠼Y(jié)構(gòu)中添加元素户辱。
* 不同的對(duì)象結(jié)構(gòu)有不同的構(gòu)建方式
* @param ele 加入到對(duì)象結(jié)構(gòu)的元素
*/
public void addElement(Element ele){
this.col.add(ele);
}
}
- 接下來(lái)看看客戶端的示意實(shí)現(xiàn)庐镐,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建ObjectStructure
ObjectStructure os = new ObjectStructure();
//創(chuàng)建要加入對(duì)象結(jié)構(gòu)的元素
Element eleA = new ConcreteElementA();
Element eleB = new ConcreteElementB();
//把元素加入對(duì)象結(jié)構(gòu)
os.addElement(eleA);
os.addElement(eleB);
//創(chuàng)建訪問(wèn)者
Visitor visitor = new ConcreteVisitor1();
//調(diào)用業(yè)務(wù)處理的方法
os.handleRequest(visitor);
}
}
2.4 使用訪問(wèn)者模式重寫(xiě)示例##
要使用訪問(wèn)者模式來(lái)重寫(xiě)示例韧献,首先就要按照訪問(wèn)者模式的結(jié)構(gòu),分離出兩個(gè)類層次來(lái)渊啰,**一個(gè)是對(duì)應(yīng)于元素的類層次绘证,一個(gè)是對(duì)應(yīng)于訪問(wèn)者的類層次**
。
對(duì)于對(duì)應(yīng)于元素的類層次魏宽,現(xiàn)在已經(jīng)有了派桩,就是客戶的對(duì)象層次
。而對(duì)應(yīng)于訪問(wèn)者的類層次员魏,現(xiàn)在還沒(méi)有逆趋,不過(guò),按照訪問(wèn)者模式的結(jié)構(gòu)
魄眉,應(yīng)該是先定義一個(gè)訪問(wèn)者接口,然后把每種業(yè)務(wù)實(shí)現(xiàn)成為一個(gè)單獨(dú)的訪問(wèn)者對(duì)象,也就是說(shuō)應(yīng)該使用一個(gè)訪問(wèn)者對(duì)象來(lái)實(shí)現(xiàn)對(duì)客戶的偏好分析宫屠,而用另外一個(gè)訪問(wèn)者對(duì)象來(lái)實(shí)現(xiàn)對(duì)客戶的價(jià)值分析浪蹂。
在分離好兩個(gè)類層次過(guò)后,為了方便客戶端的訪問(wèn)缰猴,定義一個(gè)ObjectStructure闷堡,其實(shí)就類似于前面示例中的客戶管理的業(yè)務(wù)對(duì)象
缚窿。新的示例的結(jié)構(gòu)如圖所示:
仔細(xì)查看圖所示的程序結(jié)構(gòu)示意圖吨悍,細(xì)心的朋友會(huì)發(fā)現(xiàn)葫隙,在圖上沒(méi)有出現(xiàn)對(duì)客戶進(jìn)行價(jià)值分析的功能了
。這是為了示范“使用訪問(wèn)者模式來(lái)實(shí)現(xiàn)示例功能過(guò)后糟描,可以很容易的給對(duì)象結(jié)構(gòu)增加新的功能”,所以先不做這個(gè)功能见间,等都實(shí)現(xiàn)好了,再來(lái)擴(kuò)展這個(gè)功能
。接下來(lái)還是看看代碼實(shí)現(xiàn),以更好的體會(huì)訪問(wèn)者模式叠骑。
- 先來(lái)看看Customer的代碼掉房,Customer相當(dāng)于訪問(wèn)者模式中的Element,它的實(shí)現(xiàn)跟以前相比有如下的改變:
新增一個(gè)接受訪問(wèn)者訪問(wèn)的方法哪亿;
把能夠分離出去放到訪問(wèn)者中實(shí)現(xiàn)的方法,從Customer中刪除掉篡殷,包括:客戶提出服務(wù)請(qǐng)求的方法、對(duì)客戶進(jìn)行偏好分析的方法劲弦、對(duì)客戶進(jìn)行價(jià)值分析的方法等纲仍;
示例代碼如下:
public abstract class Customer {
private String customerId;
private String name;
/**
* 接受訪問(wèn)者的訪問(wèn)
* @param visitor 訪問(wèn)者對(duì)象
*/
public abstract void accept(Visitor visitor);
}
- 看看兩種客戶的實(shí)現(xiàn)夜赵,先看企業(yè)客戶的實(shí)現(xiàn)沸版,示例代碼如下:
public class EnterpriseCustomer extends Customer{
private String linkman;
private String linkTelephone;
private String registerAddress;
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitEnterpriseCustomer(this);
}
}
再看看個(gè)人客戶的實(shí)現(xiàn)细办,示例代碼如下:
public class PersonalCustomer extends Customer{
private String telephone;
private int age;
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitPersonalCustomer(this);
}
}
- 看看訪問(wèn)者的接口定義,示例代碼如下:
/**
* 訪問(wèn)者接口
*/
public interface Visitor {
/**
* 訪問(wèn)企業(yè)客戶茴肥,相當(dāng)于給企業(yè)客戶添加訪問(wèn)者的功能
* @param ec 企業(yè)客戶的對(duì)象
*/
public void visitEnterpriseCustomer(EnterpriseCustomer ec);
/**
* 訪問(wèn)個(gè)人客戶坚踩,相當(dāng)于給個(gè)人客戶添加訪問(wèn)者的功能
* @param pc 個(gè)人客戶的對(duì)象
*/
public void visitPersonalCustomer(PersonalCustomer pc);
}
- 接下來(lái)看看各個(gè)訪問(wèn)者的實(shí)現(xiàn),每個(gè)訪問(wèn)者對(duì)象負(fù)責(zé)一類的功能處理瓤狐,先看實(shí)現(xiàn)客戶提出服務(wù)請(qǐng)求的功能的訪問(wèn)者瞬铸,示例代碼如下:
/**
* 具體的訪問(wèn)者,實(shí)現(xiàn)客戶提出服務(wù)請(qǐng)求的功能
*/
public class ServiceRequestVisitor implements Visitor {
public void visitEnterpriseCustomer(EnterpriseCustomer ec){
//企業(yè)客戶提出的具體服務(wù)請(qǐng)求
System.out.println(ec.getName()+"企業(yè)提出服務(wù)請(qǐng)求");
}
public void visitPersonalCustomer(PersonalCustomer pc){
//個(gè)人客戶提出的具體服務(wù)請(qǐng)求
System.out.println("客戶"+pc.getName()+"提出服務(wù)請(qǐng)求");
}
}
接下來(lái)看看實(shí)現(xiàn)對(duì)客戶偏好分析功能的訪問(wèn)者赴捞,示例代碼如下:
/**
* 具體的訪問(wèn)者,實(shí)現(xiàn)對(duì)客戶的偏好分析
*/
public class PredilectionAnalyzeVisitor implements Visitor {
public void visitEnterpriseCustomer(EnterpriseCustomer ec){
//根據(jù)過(guò)往購(gòu)買(mǎi)的歷史、潛在購(gòu)買(mǎi)意向
//以及客戶所在行業(yè)的發(fā)展趨勢(shì)略步、客戶的發(fā)展預(yù)期等的分析
System.out.println("現(xiàn)在對(duì)企業(yè)客戶"+ec.getName()+"進(jìn)行產(chǎn)品偏好分析");
}
public void visitPersonalCustomer(PersonalCustomer pc){
System.out.println("現(xiàn)在對(duì)個(gè)人客戶"+pc.getName()+"進(jìn)行產(chǎn)品偏好分析");
}
}
- 接下來(lái)看看ObjectStructure的實(shí)現(xiàn),示例代碼如下:
public class ObjectStructure {
/**
* 要操作的客戶集合
*/
private Collection<Customer> col = new ArrayList<Customer>();
/**
* 提供給客戶端操作的高層接口儡毕,具體的功能由客戶端傳入的訪問(wèn)者決定
* @param visitor 客戶端需要使用的訪問(wèn)者
*/
public void handleRequest(Visitor visitor){
//循環(huán)對(duì)象結(jié)構(gòu)中的元素褂萧,接受訪問(wèn)
for(Customer cm : col){
cm.accept(visitor);
}
}
/**
* 組建對(duì)象結(jié)構(gòu)卷雕,向?qū)ο蠼Y(jié)構(gòu)中添加元素太雨。
* 不同的對(duì)象結(jié)構(gòu)有不同的構(gòu)建方式
* @param ele 加入到對(duì)象結(jié)構(gòu)的元素
*/
public void addElement(Customer ele){
this.col.add(ele);
}
}
- 該來(lái)寫(xiě)個(gè)客戶端測(cè)試一下了,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建ObjectStructure
ObjectStructure os = new ObjectStructure();
//準(zhǔn)備點(diǎn)測(cè)試數(shù)據(jù)葫哗,創(chuàng)建客戶對(duì)象,并加入ObjectStructure
Customer cm1 = new EnterpriseCustomer();
cm1.setName("ABC集團(tuán)");
os.addElement(cm1);
Customer cm2 = new EnterpriseCustomer();
cm2.setName("CDE公司");
os.addElement(cm2);
Customer cm3 = new PersonalCustomer();
cm3.setName("張三");
os.addElement(cm3);
//客戶提出服務(wù)請(qǐng)求,傳入服務(wù)請(qǐng)求的Visitor
ServiceRequestVisitor srVisitor = new ServiceRequestVisitor();
os.handleRequest(srVisitor);
//要對(duì)客戶進(jìn)行偏好分析契讲,傳入偏好分析的Visitor
PredilectionAnalyzeVisitor paVisitor = new PredilectionAnalyzeVisitor();
os.handleRequest(paVisitor);
}
}
運(yùn)行結(jié)果如下:
ABC集團(tuán)企業(yè)提出服務(wù)請(qǐng)求
CDE公司企業(yè)提出服務(wù)請(qǐng)求
客戶張三提出服務(wù)請(qǐng)求
現(xiàn)在對(duì)企業(yè)客戶ABC集團(tuán)進(jìn)行產(chǎn)品偏好分析
現(xiàn)在對(duì)企業(yè)客戶CDE公司進(jìn)行產(chǎn)品偏好分析
現(xiàn)在對(duì)個(gè)人客戶張三進(jìn)行產(chǎn)品偏好分析
- 使用訪問(wèn)者模式來(lái)重新實(shí)現(xiàn)了前面示例的功能枣申,把各類相同的功能放到單獨(dú)的訪問(wèn)者對(duì)象里面贮缅,
使得代碼不再雜亂数焊,系統(tǒng)結(jié)構(gòu)也更清晰,能方便的維護(hù)了
包竹,算是解決了前面示例的一個(gè)問(wèn)題酱讶。
還有一個(gè)問(wèn)題箱叁,就是看看能不能方便的增加新的功能
,前面在示例的時(shí)候淆九,故意留下了一個(gè)對(duì)客戶進(jìn)行價(jià)值分析的功能沒(méi)有實(shí)現(xiàn),那么接下來(lái)就看看如何把這個(gè)功能增加到已有的系統(tǒng)中钾麸。在訪問(wèn)者模式中要給對(duì)象結(jié)構(gòu)增加新的功能吭净,只需要把新的功能實(shí)現(xiàn)成為訪問(wèn)者,然后在客戶端調(diào)用的時(shí)候使用這個(gè)訪問(wèn)者對(duì)象來(lái)訪問(wèn)對(duì)象結(jié)構(gòu)即可斑举。
接下來(lái)看看實(shí)現(xiàn)對(duì)客戶價(jià)值分析功能的訪問(wèn)者,示例代碼如下:
/**
* 具體的訪問(wèn)者先鱼,實(shí)現(xiàn)對(duì)客戶價(jià)值分析
*/
public class WorthAnalyzeVisitor implements Visitor {
public void visitEnterpriseCustomer(EnterpriseCustomer ec){
//根據(jù)購(gòu)買(mǎi)的金額大小更胖、購(gòu)買(mǎi)的產(chǎn)品和服務(wù)的多少薄声、購(gòu)買(mǎi)的頻率等進(jìn)行分析
//企業(yè)客戶的標(biāo)準(zhǔn)會(huì)比個(gè)人客戶的高
System.out.println("現(xiàn)在對(duì)企業(yè)客戶"+ec.getName()+"進(jìn)行價(jià)值分析");
}
public void visitPersonalCustomer(PersonalCustomer pc){
System.out.println("現(xiàn)在對(duì)個(gè)人客戶"+pc.getName()+"進(jìn)行價(jià)值分析");
}
}
使用這個(gè)功能,只要在客戶端添加如下的代碼即可,示例代碼如下:
//要對(duì)客戶進(jìn)行價(jià)值分析,傳入價(jià)值分析的Visitor
WorthAnalyzeVisitor waVisitor = new WorthAnalyzeVisitor();
os.handleRequest(waVisitor);
3 模式講解#
3.1 認(rèn)識(shí)訪問(wèn)者模式##
- 訪問(wèn)者的功能
訪問(wèn)者模式能給一系列對(duì)象巨缘,透明的添加新功能
洋只。從而避免在維護(hù)期間铭腕,對(duì)這一系列對(duì)象進(jìn)行修改佑菩,而且還能變相實(shí)現(xiàn)復(fù)用訪問(wèn)者所具有的功能
逐哈。
由于是針對(duì)一系列對(duì)象的操作畏浆,這也導(dǎo)致,如果只想給一系列對(duì)象中的部分對(duì)象添加功能觅够,就會(huì)有些麻煩恨课;而且要始終能保證把這一系列對(duì)象都要調(diào)用到,不管是循環(huán)也好,還是遞歸也好糖权,總之要讓每個(gè)對(duì)象都要被訪問(wèn)到阀坏。
- 調(diào)用通路
訪問(wèn)者之所以能實(shí)現(xiàn)“為一系列對(duì)象透明的添加新功能”封字,注意是透明的,也就是這一系列對(duì)象是不知道被添加功能的
。
重要的就是依靠通用方法
熟掂,訪問(wèn)者這邊說(shuō)要去訪問(wèn)缎浇,就提供一個(gè)訪問(wèn)的方法,如visit方法赴肚;而對(duì)象那邊說(shuō)素跺,好的,我接受你的訪問(wèn)誉券,提供一個(gè)接受訪問(wèn)的方法指厌,如accept方法。這兩個(gè)方法并不代表任何具體的功能踊跟,只是構(gòu)成一個(gè)調(diào)用的通路踩验,那么真正的功能實(shí)現(xiàn)在哪里呢?又如何調(diào)用到呢商玫?
很簡(jiǎn)單箕憾,就在accept方法里面,回調(diào)visit的方法拳昌,從而回調(diào)到訪問(wèn)者的具體實(shí)現(xiàn)上袭异,而這個(gè)訪問(wèn)者的具體實(shí)現(xiàn)的方法才是要添加的新的功能
。
- 兩次分發(fā)技術(shù)
訪問(wèn)者模式能夠?qū)崿F(xiàn)在不改變對(duì)象結(jié)構(gòu)的情況下炬藤,就能給對(duì)象結(jié)構(gòu)中的類增加功能御铃,實(shí)現(xiàn)這個(gè)效果所使用的核心技術(shù)就是兩次分發(fā)的技術(shù)
。
在訪問(wèn)者模式中沈矿,當(dāng)客戶端調(diào)用ObjectStructure的時(shí)候上真,會(huì)遍歷ObjectStructure中所有的元素,
調(diào)用這些元素的accept方法羹膳,讓這些元素來(lái)接受訪問(wèn)睡互,這是請(qǐng)求的第一次分發(fā)
;在具體的元素對(duì)象中實(shí)現(xiàn)accept方法的時(shí)候陵像,
會(huì)回調(diào)訪問(wèn)者的visit方法湃缎,等于請(qǐng)求被第二次分發(fā)了
,請(qǐng)求被分發(fā)給訪問(wèn)者來(lái)進(jìn)行處理蠢壹,真正實(shí)現(xiàn)功能的正是訪問(wèn)者的visit方法;
兩次分發(fā)技術(shù)具體的調(diào)用過(guò)程示意如圖所示:
兩次分發(fā)技術(shù)使得客戶端的請(qǐng)求不再被靜態(tài)的綁定在元素對(duì)象上
九巡,這個(gè)時(shí)候真正執(zhí)行什么樣的功能同時(shí)取決于訪問(wèn)者類型和元素類型图贸,就算是同一種元素類型,只要訪問(wèn)者類型不一樣,最終執(zhí)行的功能也不會(huì)一樣疏日,這樣一來(lái)偿洁,就可以在元素對(duì)象不變的情況下,通過(guò)改變?cè)L問(wèn)者的類型沟优,來(lái)改變真正執(zhí)行的功能涕滋。
兩次分發(fā)技術(shù)還有一個(gè)優(yōu)點(diǎn),就是可以在程序運(yùn)行期間進(jìn)行動(dòng)態(tài)的功能組裝和切換挠阁,只需要在客戶端調(diào)用時(shí)宾肺,組合使用不同的訪問(wèn)者對(duì)象實(shí)例即可。
從另一個(gè)層面思考侵俗,Java回調(diào)技術(shù)也有點(diǎn)類似于兩次分發(fā)技術(shù)锨用,客戶端調(diào)用某方法,這個(gè)方法就類似于accept方法隘谣,傳入一個(gè)接口的實(shí)現(xiàn)對(duì)象增拥,這個(gè)接口的實(shí)現(xiàn)對(duì)象就有點(diǎn)像是訪問(wèn)者,在方法內(nèi)部寻歧,會(huì)回調(diào)這個(gè)接口的方法掌栅,就類似于調(diào)用訪問(wèn)者的visit方法,最終執(zhí)行的還是接口的具體實(shí)現(xiàn)里面實(shí)現(xiàn)的功能
码泛。
- 為何不在Component中實(shí)現(xiàn)回調(diào)visit方法
在看上面的示例的時(shí)候猾封,細(xì)心的朋友會(huì)發(fā)現(xiàn),在企業(yè)客戶對(duì)象和個(gè)人客戶對(duì)象中實(shí)現(xiàn)的accept方法從表面上看是相似的弟晚,都需要回調(diào)訪問(wèn)者的方法忘衍,可能就會(huì)有朋友想,為什么不把回調(diào)訪問(wèn)者方法的調(diào)用語(yǔ)句放到父類中去卿城,那樣不就可以復(fù)用了嗎枚钓?
請(qǐng)注意,這是不可以的瑟押,雖然看起來(lái)是相似的語(yǔ)句搀捷,但其實(shí)是不同的,主要的玄機(jī)就在傳入的this身上
多望。this是代表當(dāng)前的對(duì)象實(shí)例的嫩舟,在企業(yè)客戶對(duì)象中傳遞的就是企業(yè)客戶對(duì)象的實(shí)例,在個(gè)人客戶對(duì)象中傳遞的就是個(gè)人客戶對(duì)象的實(shí)例怀偷,這樣在訪問(wèn)者的實(shí)現(xiàn)中家厌,就可以通過(guò)這不同的對(duì)象實(shí)例來(lái)訪問(wèn)不同的實(shí)例對(duì)象的數(shù)據(jù)了。
如果把這句話放到父類中椎工,那么傳遞的就是父類對(duì)象的實(shí)例饭于,是沒(méi)有子對(duì)象的數(shù)據(jù)的蜀踏,因此這句話不能放到父類中去。
- 訪問(wèn)者模式的調(diào)用順序示意圖
訪問(wèn)者模式的調(diào)用順序如圖所示:
- 空的訪問(wèn)方法
并不是所有的訪問(wèn)方法都需要實(shí)現(xiàn)掰吕,由于訪問(wèn)者模式默認(rèn)的是訪問(wèn)對(duì)象結(jié)構(gòu)中的所有元素
果覆,因此在實(shí)現(xiàn)某些功能的時(shí)候,如果不需要涉及到某些元素的訪問(wèn)方法殖熟,這些方法可以實(shí)現(xiàn)成為空的局待,比如:這個(gè)訪問(wèn)者只想要處理組合對(duì)象 ,那么訪問(wèn)葉子對(duì)象的方法就可以為空菱属,雖然還是需要訪問(wèn)所有的元素對(duì)象钳榨。
還有一種就是有條件接受訪問(wèn)
,在自己的accept方法里面進(jìn)行判斷照皆,滿足要求的接受重绷,不滿足要求的,就相當(dāng)于空的訪問(wèn)方法膜毁,什么都不用做昭卓。
3.2 操作組合對(duì)象結(jié)構(gòu)##
訪問(wèn)者模式一個(gè)很常見(jiàn)的應(yīng)用,就是和組合模式結(jié)合使用瘟滨,通過(guò)訪問(wèn)者模式來(lái)給由組合模式構(gòu)建的對(duì)象結(jié)構(gòu)增加功能候醒。
對(duì)于使用組合模式構(gòu)建的組合對(duì)象結(jié)構(gòu),對(duì)外有一個(gè)統(tǒng)一的外觀杂瘸,要想添加新的功能也不是很困難倒淫,只要在組件的接口上定義新的功能就可以了,麻煩的是這樣一來(lái)败玉,需要修改所有的子類敌土。而且,每次添加一個(gè)新功能运翼,都需要這么痛苦一回返干,修改組件接口,然后修改所有的子類血淌,這是相當(dāng)糟糕的矩欠。
為了讓組合對(duì)象結(jié)構(gòu)更靈活、更容易維護(hù)和更好的擴(kuò)展性悠夯,接下來(lái)把它改造成訪問(wèn)者模式和組合模式組合來(lái)實(shí)現(xiàn)
癌淮。這樣在今后再進(jìn)行功能改造的時(shí)候,就不需要再改動(dòng)這個(gè)組合對(duì)象結(jié)構(gòu)了沦补。
訪問(wèn)者模式和組合模式組合使用的思路:
首先把組合對(duì)象結(jié)構(gòu)中的功能方法分離出來(lái)乳蓄,雖然維護(hù)組合對(duì)象結(jié)構(gòu)的方法也可以分離出來(lái),但是為了維持組合對(duì)象結(jié)構(gòu)本身夕膀,這些方法還是放在組合對(duì)象結(jié)構(gòu)里面虚倒;然后把這些功能方法分別實(shí)現(xiàn)成為訪問(wèn)者對(duì)象匣摘,通過(guò)訪問(wèn)者模式添加到組合的對(duì)象結(jié)構(gòu)中去。
下面通過(guò)訪問(wèn)者模式和組合模式組合來(lái)實(shí)現(xiàn)如下功能:輸出對(duì)象的名稱裹刮,在組合對(duì)象的名稱前面添加“節(jié)點(diǎn):”,在葉子對(duì)象的名稱前面添加“葉子:”庞瘸。
- 先來(lái)定義訪問(wèn)者接口
訪問(wèn)者接口非常簡(jiǎn)單捧弃,只需要定義訪問(wèn)對(duì)象結(jié)構(gòu)中不同對(duì)象的方法,示例代碼如下:
/**
* 訪問(wèn)組合對(duì)象結(jié)構(gòu)的訪問(wèn)者接口
*/
public interface Visitor {
/**
* 訪問(wèn)組合對(duì)象擦囊,相當(dāng)于給組合對(duì)象添加訪問(wèn)者的功能
* @param composite 組合對(duì)象
*/
public void visitComposite(Composite composite);
/**
* 訪問(wèn)葉子對(duì)象违霞,相當(dāng)于給葉子對(duì)象添加訪問(wèn)者的功能
* @param leaf 葉子對(duì)象
*/
public void visitLeaf(Leaf leaf);
}
- 改造組合對(duì)象的定義
然后來(lái)對(duì)已有的組合對(duì)象進(jìn)行改造,添加通用的功能方法瞬场,當(dāng)然在參數(shù)上需要傳入訪問(wèn)者买鸽。先在組件定義上添加這個(gè)方法,然后到具體的實(shí)現(xiàn)類里面去實(shí)現(xiàn)贯被。除了新加這個(gè)方法外眼五,組件定義沒(méi)有其它改變,示例代碼如下:
/**
* 抽象的組件對(duì)象彤灶,相當(dāng)于訪問(wèn)者模式中的元素對(duì)象
*/
public abstract class Component {
/**
* 接受訪問(wèn)者的訪問(wèn)
* @param visitor 訪問(wèn)者對(duì)象
*/
public abstract void accept(Visitor visitor);
/**
* 向組合對(duì)象中加入組件對(duì)象
* @param child 被加入組合對(duì)象中的組件對(duì)象
*/
public void addChild(Component child) {
// 缺省實(shí)現(xiàn)看幼,拋出例外,葉子對(duì)象沒(méi)這個(gè)功能幌陕,或子組件沒(méi)有實(shí)現(xiàn)這個(gè)功能
throw new UnsupportedOperationException("對(duì)象不支持這個(gè)功能");
}
/**
* 從組合對(duì)象中移出某個(gè)組件對(duì)象
* @param child 被移出的組件對(duì)象
*/
public void removeChild(Component child) {
// 缺省實(shí)現(xiàn)诵姜,拋出例外,葉子對(duì)象沒(méi)這個(gè)功能搏熄,或子組件沒(méi)有實(shí)現(xiàn)這個(gè)功能
throw new UnsupportedOperationException("對(duì)象不支持這個(gè)功能");
}
/**
* 返回某個(gè)索引對(duì)應(yīng)的組件對(duì)象
* @param index 需要獲取的組件對(duì)象的索引棚唆,索引從0開(kāi)始
* @return 索引對(duì)應(yīng)的組件對(duì)象
*/
public Component getChildren(int index) {
throw new UnsupportedOperationException("對(duì)象不支持這個(gè)功能");
}
}
- 實(shí)現(xiàn)組合對(duì)象和葉子對(duì)象
改變了組件定義,那么需要在組合類和葉子類上分別實(shí)現(xiàn)這個(gè)方法心例,組合類中實(shí)現(xiàn)的時(shí)候宵凌,通常會(huì)循環(huán)讓所有的子元素都接受訪問(wèn),這樣才能為所有的對(duì)象都添加上新的功能
契邀,示例代碼如下:
/**
* 組合對(duì)象摆寄,可以包含其它組合對(duì)象或者葉子對(duì)象,
* 相當(dāng)于訪問(wèn)者模式的具體Element實(shí)現(xiàn)對(duì)象
*/
public class Composite extends Component{
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitComposite(this);
//循環(huán)子元素坯门,讓子元素也接受訪問(wèn)
for(Component c : childComponents){
//調(diào)用子對(duì)象接受訪問(wèn)微饥,變相實(shí)現(xiàn)遞歸
c.accept(visitor);
}
}
/**
* 用來(lái)存儲(chǔ)組合對(duì)象中包含的子組件對(duì)象
*/
private List<Component> childComponents = new ArrayList<Component>();
/**
* 組合對(duì)象的名字
*/
private String name = "";
/**
* 構(gòu)造方法,傳入組合對(duì)象的名字
* @param name 組合對(duì)象的名字
*/
public Composite(String name){
this.name = name;
}
public void addChild(Component child) {
childComponents.add(child);
}
public String getName() {
return name;
}
}
葉子對(duì)象的基本實(shí)現(xiàn)古戴,示例代碼如下:
/**
* 葉子對(duì)象欠橘,相當(dāng)于訪問(wèn)者模式的具體Element實(shí)現(xiàn)對(duì)象
*/
public class Leaf extends Component{
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitLeaf(this);
}
/**
* 葉子對(duì)象的名字
*/
private String name = "";
/**
* 構(gòu)造方法,傳入葉子對(duì)象的名字
* @param name 葉子對(duì)象的名字
*/
public Leaf(String name){
this.name = name;
}
public String getName() {
return name;
}
}
- 實(shí)現(xiàn)一個(gè)訪問(wèn)者
組合對(duì)象結(jié)構(gòu)已經(jīng)改造好了现恼,現(xiàn)在需要提供一個(gè)訪問(wèn)者的實(shí)現(xiàn)肃续,它會(huì)實(shí)現(xiàn)真正的功能黍檩,也就是要添加到對(duì)象結(jié)構(gòu)中的功能。示例代碼如下:
/**
* 具體的訪問(wèn)者始锚,實(shí)現(xiàn):輸出對(duì)象的名稱刽酱,在組合對(duì)象的名稱前面添加"節(jié)點(diǎn):",
* 在葉子對(duì)象的名稱前面添加"葉子:"
*/
public class PrintNameVisitor implements Visitor {
public void visitComposite(Composite composite) {
//訪問(wèn)到組合對(duì)象的數(shù)據(jù)
System.out.println("節(jié)點(diǎn):"+composite.getName());
}
public void visitLeaf(Leaf leaf) {
//訪問(wèn)到葉子對(duì)象的數(shù)據(jù)
System.out.println("葉子:"+leaf.getName());
}
}
- 訪問(wèn)所有元素對(duì)象的對(duì)象——ObjectStructure
訪問(wèn)者是給一系列對(duì)象添加功能的瞧捌,因此一個(gè)訪問(wèn)者需要訪問(wèn)所有的對(duì)象
棵里,為了方便遍歷整個(gè)對(duì)象結(jié)構(gòu),通常會(huì)定義一個(gè)專門(mén)的類出來(lái)姐呐,在這個(gè)類里面進(jìn)行元素迭代訪問(wèn)殿怜,同時(shí)這個(gè)類提供客戶端訪問(wèn)元素的接口。
對(duì)于這個(gè)示例曙砂,由于在組合對(duì)象結(jié)構(gòu)里面头谜,已經(jīng)實(shí)現(xiàn)了對(duì)象結(jié)構(gòu)的遍歷,本來(lái)是可以不需要這個(gè)ObjectStructure的鸠澈,但是為了更清晰的展示訪問(wèn)者模式的結(jié)構(gòu)柱告,也為了今后的擴(kuò)展或?qū)崿F(xiàn)方便,還是定義一個(gè)ObjectStructure款侵。示例代碼如下:
/**
* 對(duì)象結(jié)構(gòu),通常在這里對(duì)元素對(duì)象進(jìn)行遍歷末荐,讓訪問(wèn)者能訪問(wèn)到所有的元素
*/
public class ObjectStructure {
/**
* 表示對(duì)象結(jié)構(gòu),可以是一個(gè)組合結(jié)構(gòu)
*/
private Component root = null;
/**
* 提供給客戶端操作的高層接口
* @param visitor 客戶端需要使用的訪問(wèn)者
*/
public void handleRequest(Visitor visitor){
//讓組合對(duì)象結(jié)構(gòu)中的根元素新锈,接受訪問(wèn)
//在組合對(duì)象結(jié)構(gòu)中已經(jīng)實(shí)現(xiàn)了元素的遍歷
if(root!=null){
root.accept(visitor);
}
}
/**
* 傳入組合對(duì)象結(jié)構(gòu)
* @param ele 組合對(duì)象結(jié)構(gòu)
*/
public void setRoot(Component ele){
this.root = ele;
}
}
- 寫(xiě)個(gè)客戶端甲脏,來(lái)看看如何通過(guò)訪問(wèn)者去為對(duì)象結(jié)構(gòu)添加新的功能,示例代碼如下:
public class Client {
public static void main(String[] args) {
//定義所有的組合對(duì)象
Component root = new Composite("服裝");
Component c1 = new Composite("男裝");
Component c2 = new Composite("女裝");
//定義所有的葉子對(duì)象
Component leaf1 = new Leaf("襯衣");
Component leaf2 = new Leaf("夾克");
Component leaf3 = new Leaf("裙子");
Component leaf4 = new Leaf("套裝");
//按照樹(shù)的結(jié)構(gòu)來(lái)組合組合對(duì)象和葉子對(duì)象
root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);
//創(chuàng)建ObjectStructure
ObjectStructure os = new ObjectStructure();
os.setRoot(root);
//調(diào)用ObjectStructure來(lái)處理請(qǐng)求功能
Visitor psVisitor = new PrintNameVisitor();
os.handleRequest(psVisitor);
}
}
輸出的效果如下:
節(jié)點(diǎn):服裝
節(jié)點(diǎn):男裝
葉子:襯衣
葉子:夾克
節(jié)點(diǎn):女裝
葉子:裙子
葉子:套裝
看看結(jié)果妹笆,是不是期望的那樣呢块请?
好好體會(huì)一下,想想訪問(wèn)者模式是如何實(shí)現(xiàn)動(dòng)態(tài)的給組件添加功能的拳缠?尤其是要想想墩新,實(shí)現(xiàn)的機(jī)制是什么?真正實(shí)現(xiàn)新功能的地方在哪里窟坐?
- 現(xiàn)在的程序結(jié)構(gòu)
前面是分步的示范海渊,大家已經(jīng)體會(huì)了一番,接下來(lái)小結(jié)一下哲鸳。
如同前面的示例臣疑,訪問(wèn)者的方法就相當(dāng)于作用于組合對(duì)象結(jié)構(gòu)中各個(gè)元素的操作,是一種通用的表達(dá)徙菠,同樣的訪問(wèn)者接口和同樣的方法讯沈,只要提供不同的訪問(wèn)者具體實(shí)現(xiàn),就表示不同的功能
婿奔。
同時(shí)在組合對(duì)象中缺狠,接受訪問(wèn)的方法问慎,也是一個(gè)通用的表達(dá),不管你是什么樣的功能挤茄,統(tǒng)統(tǒng)接受就好了如叼,然后回調(diào)回去執(zhí)行真正的功能。這樣一來(lái)穷劈,各元素的類就不用再修改了薇正,只要提供不同的訪問(wèn)者實(shí)現(xiàn),然后通過(guò)這個(gè)通用表達(dá)囚衔,就結(jié)合到組合對(duì)象中來(lái)了,就相當(dāng)于給所有的對(duì)象提供了新的功能
雕沿。
示例的整體結(jié)構(gòu)练湿,如圖所示:
3.3 誰(shuí)負(fù)責(zé)遍歷所有元素對(duì)象##
在訪問(wèn)者模式中,訪問(wèn)者必須要能夠訪問(wèn)到對(duì)象結(jié)構(gòu)中的每個(gè)對(duì)象审轮,因?yàn)樵L問(wèn)者要為每個(gè)對(duì)象添加功能
肥哎,為此特別在模式中定義出一個(gè)ObjectStructure來(lái),然后由ObjectStructure來(lái)負(fù)責(zé)遍歷訪問(wèn)一系列對(duì)象中的每個(gè)對(duì)象疾渣。
- 在ObjectStructure迭代所有的元素時(shí)篡诽,又分成兩種情況。
一種是元素的對(duì)象結(jié)構(gòu)是通過(guò)集合來(lái)組織的榴捡,那么直接在ObjectStructure中對(duì)集合進(jìn)行迭代
杈女,對(duì)每一個(gè)元素調(diào)用accept就好了。
另一種情況是元素的對(duì)象結(jié)構(gòu)是通過(guò)組合模式來(lái)組織的吊圾,通炒镆可以構(gòu)成對(duì)象樹(shù),這種情況一般就不需要在ObjectStructure中迭代了
项乒,而通常的做法是在組合對(duì)象的accept方法里面啰劲,遞歸遍歷它的子元素,然后調(diào)用子元素的accept方法檀何。
- 不需要ObjectStructure的時(shí)候
在實(shí)際開(kāi)發(fā)中蝇裤,有一種典型的情況可以不需要ObjectStructure對(duì)象,那就是只有一個(gè)被訪問(wèn)對(duì)象的時(shí)候
频鉴。只有一個(gè)被訪問(wèn)對(duì)象栓辜,當(dāng)然就不需要使用ObjectStructure來(lái)組合和迭代了,只要調(diào)用這個(gè)對(duì)象就好了砚殿。
事實(shí)上還有一種情況也可以不使用ObjectStructure啃憎,比如上面訪問(wèn)的組合對(duì)象結(jié)構(gòu),從客戶端的角度看似炎,他訪問(wèn)的其實(shí)就是一個(gè)對(duì)象辛萍,因此可以把ObjectStructure去掉悯姊,然后直接從客戶端調(diào)用元素的accept方法。
還是通過(guò)示例來(lái)說(shuō)明贩毕,先把ObjectStructure類去掉悯许,由于沒(méi)有了ObjectStructure,那么客戶端調(diào)用的時(shí)候就直接調(diào)用組合對(duì)象結(jié)構(gòu)的根元素的accept方法
辉阶,示例代碼如下:
public class Client {
public static void main(String[] args) {
//定義組件數(shù)據(jù)先壕,組裝對(duì)象樹(shù),跟剛才的測(cè)試一樣谆甜,這里就省略了
Visitor psVisitor = new PrintNameVisitor();
root.accept(psVisitor);
}
}
- 有些時(shí)候垃僚,
遍歷元素的方法也可以放到訪問(wèn)者當(dāng)中去,當(dāng)然也是需要遞歸遍歷它的子元素的
规辱。出現(xiàn)這種情況的主要原因是:想在訪問(wèn)者中實(shí)現(xiàn)特別復(fù)雜的遍歷谆棺,訪問(wèn)者的實(shí)現(xiàn)依賴于對(duì)象結(jié)構(gòu)的操作結(jié)果。
使用訪問(wèn)者模式和組合模式組合來(lái)實(shí)現(xiàn)了輸出名稱的功能罕袋,如果現(xiàn)在要實(shí)現(xiàn)把組合的對(duì)象結(jié)構(gòu)按照樹(shù)的形式輸出改淑,就是按照在組合模式中示例的那樣,輸出如下的樹(shù)形結(jié)構(gòu):
+服裝
+男裝
-襯衣
-夾克
+女裝
-裙子
-套裝
要實(shí)現(xiàn)這個(gè)功能浴讯,在組合對(duì)象結(jié)構(gòu)中去遍歷子對(duì)象的方式就比較難于實(shí)現(xiàn)朵夏,因?yàn)橐敵鲞@個(gè)樹(shù)形結(jié)構(gòu),需要控制每個(gè)對(duì)象在輸出的時(shí)候榆纽,向后的退格數(shù)量仰猖,這個(gè)需要在對(duì)象結(jié)構(gòu)的循環(huán)中來(lái)控制,這種功能可以選擇在訪問(wèn)者當(dāng)中去遍歷對(duì)象結(jié)構(gòu)奈籽。
來(lái)改造上面的示例亮元,看看通過(guò)訪問(wèn)者來(lái)遍歷元素如何實(shí)現(xiàn)這樣的功能。
首先在Composite的accept實(shí)現(xiàn)中去除掉遞歸調(diào)用子對(duì)象的代碼唠摹,同時(shí)添加一個(gè)讓訪問(wèn)者訪問(wèn)到其所包含的子對(duì)象的方法爆捞,示例代碼如下:
public class Composite extends Component {
//其它相同部分就省略了,只看變化的方法
public void accept(Visitor visitor) {
//回調(diào)訪問(wèn)者對(duì)象的相應(yīng)方法
visitor.visitComposite(this);
// for(Component c : childComponents) {
// // 調(diào)用子對(duì)象接受訪問(wèn)勾拉,變相實(shí)現(xiàn)遞歸
// c.accept(visitor);
// }
}
public List<Component> getChildComponents() {
return childComponents;
}
}
然后新實(shí)現(xiàn)一個(gè)訪問(wèn)者對(duì)象煮甥,在相應(yīng)的visit實(shí)現(xiàn)里面,添加遞歸迭代所有子對(duì)象藕赞,示例代碼如下:
/**
* 具體的訪問(wèn)者成肘,實(shí)現(xiàn):輸出組合對(duì)象自身的結(jié)構(gòu)
*/
public class PrintStructVisitor implements Visitor {
/**
* 用來(lái)累計(jì)記錄對(duì)象需要向后退的格
*/
private String preStr = "";
public void visitComposite(Composite composite) {
//先把自己輸出去
System.out.println(preStr+"+"+composite.getName());
//如果還包含有子組件,那么就輸出這些子組件對(duì)象
if(composite.getChildComponents()!=null){
//然后添加一個(gè)空格斧蜕,表示向后縮進(jìn)一個(gè)空格
preStr+=" ";
//輸出當(dāng)前對(duì)象的子對(duì)象了
for(Component c : composite.getChildComponents()){
//遞歸輸出每個(gè)子對(duì)象
c.accept(this);
}
//把循環(huán)子對(duì)象所多加入的一個(gè)退格給去掉
preStr = preStr.substring(0,preStr.length()-1);
}
}
public void visitLeaf(Leaf leaf) {
//訪問(wèn)到葉子對(duì)象的數(shù)據(jù)
System.out.println(preStr+"-"+leaf.getName());
}
}
寫(xiě)個(gè)客戶端來(lái)測(cè)試一下看看双霍,是否能實(shí)現(xiàn)要求的功能。示例代碼如下:
public class Client {
public static void main(String[] args) {
//定義所有的組合對(duì)象過(guò)程跟上一個(gè)client是一樣的,這里省略了
//調(diào)用根元素的方法來(lái)接受請(qǐng)求功能
Visitor psVisitor = new PrintStructVisitor();
root.accept(psVisitor);
}
}
3.4 訪問(wèn)者模式優(yōu)缺點(diǎn)##
- 好的擴(kuò)展性
能夠在不修改對(duì)象結(jié)構(gòu)中的元素的情況下洒闸,給對(duì)象結(jié)構(gòu)中的元素添加新的功能染坯。
- 好的復(fù)用性
可以通過(guò)訪問(wèn)者來(lái)定義整個(gè)對(duì)象結(jié)構(gòu)通用的功能,從而提高復(fù)用程度丘逸。
- 分離無(wú)關(guān)行為
可以通過(guò)訪問(wèn)者來(lái)分離無(wú)關(guān)的行為单鹿,把相關(guān)的行為封裝在一起,構(gòu)成一個(gè)訪問(wèn)者深纲,這樣每一個(gè)訪問(wèn)者的功能都比較單一仲锄。
- 對(duì)象結(jié)構(gòu)變化很困難
不適用于對(duì)象結(jié)構(gòu)中的類經(jīng)常變化的情況,因?yàn)閷?duì)象結(jié)構(gòu)發(fā)生了改變湃鹊,訪問(wèn)者的接口和訪問(wèn)者的實(shí)現(xiàn)都要發(fā)生相應(yīng)的改變儒喊,代價(jià)太高。
- 破壞封裝
訪問(wèn)者模式通常需要對(duì)象結(jié)構(gòu)開(kāi)放內(nèi)部數(shù)據(jù)給訪問(wèn)者和ObjectStructrue币呵,這破壞了對(duì)象的封裝性澄惊。
3.5 思考訪問(wèn)者模式##
- 訪問(wèn)者模式的本質(zhì)
訪問(wèn)者模式的本質(zhì):預(yù)留通路,回調(diào)實(shí)現(xiàn)富雅。
仔細(xì)思考訪問(wèn)者模式,它的實(shí)現(xiàn)主要就是通過(guò)預(yù)先定義好調(diào)用的通路肛搬,在被訪問(wèn)的對(duì)象上定義accept方法没佑,在訪問(wèn)者的對(duì)象上定義visit方法;然后在調(diào)用真正發(fā)生的時(shí)候温赔,通過(guò)兩次分發(fā)的技術(shù)蛤奢,利用預(yù)先定義好的通路,回調(diào)到訪問(wèn)者具體的實(shí)現(xiàn)上
陶贼。
明白了訪問(wèn)者模式的本質(zhì)啤贩,就可以在定義一些通用功能,或者設(shè)計(jì)工具類的時(shí)候讓訪問(wèn)者模式派上大用場(chǎng)了
拜秧。你可以把已經(jīng)實(shí)現(xiàn)好的一些功能痹屹,把它們作為已有的對(duì)象結(jié)構(gòu),因?yàn)樵诮窈罂赡軙?huì)根據(jù)實(shí)際需要給它們?cè)黾有碌墓δ芡鞯踔聊阆M_(kāi)放接口來(lái)讓其它開(kāi)發(fā)人員擴(kuò)展這些功能志衍,那么你就可以用訪問(wèn)者模式來(lái)設(shè)計(jì),在這個(gè)對(duì)象結(jié)構(gòu)上預(yù)留好通用的調(diào)用通路聊替,在以后添加功能楼肪,或者是其它開(kāi)發(fā)人員來(lái)擴(kuò)展的時(shí)候,只需要提供新的訪問(wèn)者實(shí)現(xiàn)惹悄,就能夠很好的加入到系統(tǒng)中來(lái)了春叫。
- 何時(shí)選用訪問(wèn)者模式
建議在如下情況中,選用訪問(wèn)者模式:
如果想對(duì)一個(gè)對(duì)象結(jié)構(gòu),實(shí)施一些依賴于對(duì)象結(jié)構(gòu)中的具體類的操作暂殖,可以使用訪問(wèn)者模式价匠。
如果想對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的各個(gè)元素,進(jìn)行很多不同的而且不相關(guān)的操作央星,為了避免這些操作使得類變得雜亂霞怀,可以使用訪問(wèn)者模式,把這些操作分散到不同的訪問(wèn)者對(duì)象中去莉给,每個(gè)訪問(wèn)者對(duì)象實(shí)現(xiàn)同一類功能毙石。
如果對(duì)象結(jié)構(gòu)很少變動(dòng),但是需要經(jīng)常給對(duì)象結(jié)構(gòu)中的元素對(duì)象定義新的操作颓遏,可以使用訪問(wèn)者模式徐矩。
3.6 相關(guān)模式##
- 訪問(wèn)者模式和組合模式
這兩個(gè)模式可以組合使用。
如同前面示例的那樣叁幢,通過(guò)訪問(wèn)者模式給組合對(duì)象預(yù)留下擴(kuò)展功能的接口滤灯,使得給組合模式的對(duì)象結(jié)構(gòu)添加功能非常容易。
- 訪問(wèn)者模式和裝飾模式
這兩個(gè)模式從表面看功能有些相似曼玩,都能夠?qū)崿F(xiàn)在不修改原對(duì)象結(jié)構(gòu)的情況下修改原對(duì)象的功能
鳞骤。但是裝飾模式更多的是實(shí)現(xiàn)對(duì)已有功能加強(qiáng)、或者修改黍判、或者完全全新實(shí)現(xiàn)
豫尽;而訪問(wèn)者模式更多的是實(shí)現(xiàn)給對(duì)象結(jié)構(gòu)添加新的功能
。
- 訪問(wèn)者模式和解釋器模式
這兩個(gè)模式可以組合使用顷帖。
解釋器模式在構(gòu)建抽象語(yǔ)法樹(shù)的時(shí)候美旧,是使用組合模式來(lái)構(gòu)建的
,也就是說(shuō)解釋器模式解釋并執(zhí)行的抽象語(yǔ)法樹(shù)是一個(gè)組合對(duì)象結(jié)構(gòu)
贬墩,這個(gè)組合對(duì)象結(jié)構(gòu)是很少變動(dòng)的榴嗅,但是可能經(jīng)常需要為解釋器增加新的功能
,實(shí)現(xiàn)對(duì)同一對(duì)象結(jié)構(gòu)的不同解釋和執(zhí)行的功能陶舞,這正好是訪問(wèn)者模式的優(yōu)勢(shì)所在嗽测,因此這在使用解釋器模式的時(shí)候通常會(huì)組合訪問(wèn)者模式來(lái)使用。