【行為型模式十九】訪問(wèn)者模式(Visitor)

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)如圖所示:

已有的客戶管理程序結(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)如圖所示:

擴(kuò)展客戶管理功能的結(jié)構(gòu)示意圖
  1. 先看看抽象的父類,主要就是加入了兩個(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();
}
  1. 接下來(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à)值分析");
    }
}
  1. 接下來(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à)值分析");
    }
}
  1. 如何使用上面實(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)者模式呢?

  1. 訪問(wèn)者模式定義
訪問(wèn)者模式定義
  1. 應(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)如圖所示:

訪問(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)者模式示例代碼##

  1. 首先需要定義一個(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);
}
  1. 看看抽象的元素對(duì)象定義翎苫,示例代碼如下:
/**
 * 被訪問(wèn)的元素的接口
 */
public abstract class Element {
    /**
     * 接受訪問(wèn)者的訪問(wèn)
     * @param visitor 訪問(wèn)者對(duì)象
     */
    public abstract void accept(Visitor visitor);
}
  1. 接下來(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)
    }
}
  1. 接下來(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();
    }
}
  1. 該來(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);
    }
}
  1. 接下來(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)如圖所示:

新的示例的結(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)者模式叠骑。

  1. 先來(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);
}
  1. 看看兩種客戶的實(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);
    }
}
  1. 看看訪問(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);
}
  1. 接下來(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)品偏好分析");
    }
}
  1. 接下來(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);
    }
}
  1. 該來(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)品偏好分析
  1. 使用訪問(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)者模式##

  1. 訪問(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)到阀坏。

  1. 調(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)的方法才是要添加的新的功能

  1. 兩次分發(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ā)

兩次分發(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)的功能码泛。

  1. 為何不在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ù)的蜀踏,因此這句話不能放到父類中去。

  1. 訪問(wèn)者模式的調(diào)用順序示意圖

訪問(wèn)者模式的調(diào)用順序如圖所示:

訪問(wèn)者模式的調(diào)用順序
  1. 空的訪問(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ì)象的名稱前面添加“葉子:”庞瘸。

  1. 先來(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);
}
  1. 改造組合對(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è)功能");
    }
}
  1. 實(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;
    }
}
  1. 實(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());
    }
}
  1. 訪問(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;
    }
}
  1. 寫(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)新功能的地方在哪里窟坐?

  1. 現(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)练湿,如圖所示:

訪問(wèn)者模式結(jié)合組合模式的示例的結(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ì)象疾渣。

  1. 在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方法檀何。

  1. 不需要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);
    }
}
  1. 有些時(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)##

  1. 好的擴(kuò)展性

能夠在不修改對(duì)象結(jié)構(gòu)中的元素的情況下洒闸,給對(duì)象結(jié)構(gòu)中的元素添加新的功能染坯。

  1. 好的復(fù)用性

可以通過(guò)訪問(wèn)者來(lái)定義整個(gè)對(duì)象結(jié)構(gòu)通用的功能,從而提高復(fù)用程度丘逸。

  1. 分離無(wú)關(guān)行為

可以通過(guò)訪問(wèn)者來(lái)分離無(wú)關(guān)的行為单鹿,把相關(guān)的行為封裝在一起,構(gòu)成一個(gè)訪問(wèn)者深纲,這樣每一個(gè)訪問(wèn)者的功能都比較單一仲锄。

  1. 對(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à)太高。

  1. 破壞封裝

訪問(wèn)者模式通常需要對(duì)象結(jié)構(gòu)開(kāi)放內(nèi)部數(shù)據(jù)給訪問(wèn)者和ObjectStructrue币呵,這破壞了對(duì)象的封裝性澄惊。

3.5 思考訪問(wèn)者模式##

  1. 訪問(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)了春叫。

  1. 何時(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)模式##

  1. 訪問(wèn)者模式和組合模式

這兩個(gè)模式可以組合使用。

如同前面示例的那樣叁幢,通過(guò)訪問(wèn)者模式給組合對(duì)象預(yù)留下擴(kuò)展功能的接口滤灯,使得給組合模式的對(duì)象結(jié)構(gòu)添加功能非常容易。

  1. 訪問(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)添加新的功能

  1. 訪問(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)使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肿孵,一起剝皮案震驚了整個(gè)濱河市论咏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颁井,老刑警劉巖厅贪,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雅宾,居然都是意外死亡养涮,警方通過(guò)查閱死者的電腦和手機(jī)葵硕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贯吓,“玉大人懈凹,你說(shuō)我怎么就攤上這事∏男常” “怎么了介评?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)爬舰。 經(jīng)常有香客問(wèn)我们陆,道長(zhǎng),這世上最難降的妖魔是什么情屹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任坪仇,我火速辦了婚禮,結(jié)果婚禮上垃你,老公的妹妹穿的比我還像新娘椅文。我一直安慰自己,他們只是感情好惜颇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布皆刺。 她就那樣靜靜地躺著,像睡著了一般凌摄。 火紅的嫁衣襯著肌膚如雪羡蛾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天望伦,我揣著相機(jī)與錄音,去河邊找鬼煎殷。 笑死屯伞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豪直。 我是一名探鬼主播劣摇,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼弓乙!你這毒婦竟也來(lái)了末融?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暇韧,失蹤者是張志新(化名)和其女友劉穎勾习,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體懈玻,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巧婶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艺栈。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡英岭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出湿右,到底是詐尸還是另有隱情诅妹,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布毅人,位于F島的核電站吭狡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏堰塌。R本人自食惡果不足惜赵刑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望场刑。 院中可真熱鬧般此,春花似錦、人聲如沸牵现。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞎疼。三九已至科乎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贼急,已是汗流浹背茅茂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留太抓,地道東北人空闲。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像走敌,于是被迫代替她去往敵國(guó)和親碴倾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.初識(shí)訪問(wèn)者模式 表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作掉丽。它使你可以在不改變各元素的類的前提下定義作用于這些元...
    王偵閱讀 537評(píng)論 0 0
  • 【學(xué)習(xí)難度:★★☆☆☆跌榔,使用頻率:★★★☆☆】直接出處:訪問(wèn)者模式梳理和學(xué)習(xí):https://github.com...
    BruceOuyang閱讀 894評(píng)論 2 2
  • 訪問(wèn)者模式(行為型) 一、相關(guān)概述 訪問(wèn)者模式是一種較為復(fù)雜的行為型設(shè)計(jì)模式捶障,它包含訪問(wèn)者和被訪問(wèn)元素兩個(gè)主要組成...
    哈哈大圣閱讀 352評(píng)論 0 0
  • 0.提前說(shuō)明 模式選擇的方法1)模式的功能——看是否能解決問(wèn)題2)模式的本質(zhì)——看模式是否主要用來(lái)解決這類問(wèn)題3)...
    王偵閱讀 1,053評(píng)論 0 1
  • 很有緣分僧须,阿滿和我是同一天進(jìn)公司的,更巧的是她的辦公桌就在我旁邊项炼,這樣我每天都可以嘗到她做的菜皆辽,實(shí)在是美味柑蛇。 阿滿...
    12點(diǎn)樹(shù)洞閱讀 303評(píng)論 0 3