【行為型模式十四】責(zé)任鏈模式(Chain of Responsibility)

1 場景問題#

1.1 申請(qǐng)聚餐費(fèi)用##

來考慮這樣一個(gè)功能:申請(qǐng)聚餐費(fèi)用的管理。

很多公司都有這樣的福利,就是項(xiàng)目組或者是部門可以向公司申請(qǐng)一些聚餐費(fèi)用,用于組織項(xiàng)目組成員或者是部門成員進(jìn)行聚餐活動(dòng),以增進(jìn)人員之間的情感,更有利于工作中的相互合作。

申請(qǐng)聚餐費(fèi)用的大致流程一般是:由申請(qǐng)人先填寫申請(qǐng)單,然后交給領(lǐng)導(dǎo)審查,如果申請(qǐng)批準(zhǔn)下來了,領(lǐng)導(dǎo)會(huì)通知申請(qǐng)人審批通過,然后申請(qǐng)人去財(cái)務(wù)核領(lǐng)費(fèi)用,如果沒有核準(zhǔn),領(lǐng)導(dǎo)會(huì)通知申請(qǐng)人審批未通過,此事也就此作罷了。

不同級(jí)別的領(lǐng)導(dǎo),對(duì)于審批的額度是不一樣的,比如:項(xiàng)目經(jīng)理只能審批500元以內(nèi)的申請(qǐng);部門經(jīng)理能審批1000元以內(nèi)的申請(qǐng);而總經(jīng)理可以審核任意額度的申請(qǐng)。

也就是說,當(dāng)某人提出聚餐費(fèi)用申請(qǐng)的請(qǐng)求后,該請(qǐng)求會(huì)由項(xiàng)目經(jīng)理、部門經(jīng)理、總經(jīng)理之中的某一位領(lǐng)導(dǎo)來進(jìn)行相應(yīng)的處理,但是提出申請(qǐng)的人并不知道最終會(huì)由誰來處理他的請(qǐng)求,一般申請(qǐng)人是把自己的申請(qǐng)?zhí)峤唤o項(xiàng)目經(jīng)理,或許最后是由總經(jīng)理來處理他的請(qǐng)求,但是申請(qǐng)人并不知道應(yīng)該由總經(jīng)理來處理他的申請(qǐng)請(qǐng)求。

那么該怎樣實(shí)現(xiàn)這樣的功能呢?

1.2 不用模式的解決方案##

分析上面要實(shí)現(xiàn)的功能,主要就是要根據(jù)申請(qǐng)費(fèi)用的多少,然后讓不同的領(lǐng)導(dǎo)來進(jìn)行處理就可以實(shí)現(xiàn)了。也就是有點(diǎn)邏輯判斷而已,示例代碼如下:

/**
 * 處理聚餐費(fèi)用申請(qǐng)的對(duì)象
 */
public class FeeRequest {
    /**
     * 提交聚餐費(fèi)用申請(qǐng)給項(xiàng)目經(jīng)理
     * @param user 申請(qǐng)人
     * @param fee 申請(qǐng)費(fèi)用
     * @return 成功或失敗的具體通知
     */
    public String requestToProjectManager(String user,double fee){
        String str = "";
        if(fee < 500){
            //項(xiàng)目經(jīng)理的權(quán)限比較小,只能在500以內(nèi)
            str = this.projectHandle(user, fee);
        }else if(fee < 1000){
            //部門經(jīng)理的權(quán)限只能在1000以內(nèi)
            str = this.depManagerHandle(user, fee);
        }else if(fee >= 1000){
            //總經(jīng)理的權(quán)限很大,只要請(qǐng)求到了這里,他都可以處理
            str = this.generalManagerHandle(user, fee);
        }
        return str;
    }
    /**
     * 項(xiàng)目經(jīng)理審批費(fèi)用申請(qǐng),參數(shù)、返回值和上面是一樣的,省略了
     */
    private String projectHandle(String user, double fee) {
        String str = "";
        //為了測試,簡單點(diǎn),只同意小李的
        if("小李".equals(user)){
            str = "項(xiàng)目經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
        }else{
            //其它人一律不同意
            str = "項(xiàng)目經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
        }
        return str;
    }
    /**
     * 部門經(jīng)理審批費(fèi)用申請(qǐng),參數(shù)、返回值和上面是一樣的,省略了
     */
    private String depManagerHandle(String user, double fee) {
        String str = "";
        //為了測試,簡單點(diǎn),只同意小李申請(qǐng)的
        if("小李".equals(user)){
            str = "部門經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
        }else{
            //其它人一律不同意
            str= "部門經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
        }
        return str;
     }
    /**
     * 總經(jīng)理審批費(fèi)用申請(qǐng),參數(shù)、返回值和上面是一樣的,省略了
     */
    private String generalManagerHandle(String user, double fee) {
       String str = "";
      
       //為了測試,簡單點(diǎn),只同意小李的
       if("小李".equals(user)){
           str = "總經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
       }else{
           //其它人一律不同意
           str = "總經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
       }
       return str;
    }
}

寫個(gè)客戶端來測試看看效果,示例代碼如下:

public class Client {
    public static void main(String[] args) {
        FeeRequest request = new FeeRequest();
      
        //開始測試
        String ret1 = request.requestToProjectManager("小李", 300);
        System.out.println("the ret="+ret1);     
        String ret2 = request.requestToProjectManager("小張", 300);
        System.out.println("the ret="+ret2);
      
        String ret3 = request.requestToProjectManager("小李", 600);
        System.out.println("the ret="+ret3);     
        String ret4 = request.requestToProjectManager("小張", 600);
        System.out.println("the ret="+ret4);
      
        String ret5 = request.requestToProjectManager("小李", 1200);
        System.out.println("the ret="+ret5);     
        String ret6 = request.requestToProjectManager("小張", 1200);
        System.out.println("the ret="+ret6);
    }
}

運(yùn)行結(jié)果如下:

the ret1=項(xiàng)目經(jīng)理同意小李聚餐費(fèi)用300.0元的請(qǐng)求
the ret2=項(xiàng)目經(jīng)理不同意小張聚餐費(fèi)用300.0元的請(qǐng)求
the ret3=部門經(jīng)理同意小李聚餐費(fèi)用600.0元的請(qǐng)求
the ret4=部門經(jīng)理不同意小張聚餐費(fèi)用600.0元的請(qǐng)求
the ret5=總經(jīng)理同意小李聚餐費(fèi)用1200.0元的請(qǐng)求
the ret6=總經(jīng)理不同意小張聚餐費(fèi)用1200.0元的請(qǐng)求

1.3 有何問題##

上面的實(shí)現(xiàn)很簡單,基本上沒有什么特別的難度。仔細(xì)想想,這么實(shí)現(xiàn)有沒有問題呢?仔細(xì)分析申請(qǐng)聚餐費(fèi)用的業(yè)務(wù)功能和目前的實(shí)現(xiàn),主要面臨著如下問題:

聚餐費(fèi)用申請(qǐng)的處理流程是可能會(huì)變動(dòng)的。

比如現(xiàn)在的處理流程是:提交申請(qǐng)給項(xiàng)目經(jīng)理,看看是否適合由項(xiàng)目經(jīng)理處理,如果不是,看看是否適合由部門經(jīng)理處理,如果不是,看看是否適合由總經(jīng)理處理的步驟。今后可能會(huì)變化成:直接提交給部門經(jīng)理,看看是否適合由部門經(jīng)理處理,如果不是,總經(jīng)理處理這樣的步驟。也就是說,對(duì)于聚餐費(fèi)用申請(qǐng),要求處理的邏輯步驟是靈活的。

各個(gè)處理環(huán)節(jié)的業(yè)務(wù)處理也是會(huì)變動(dòng)的。

因?yàn)樘幚砹鞒炭赡馨l(fā)生變化,也會(huì)導(dǎo)致某些步驟的具體的業(yè)務(wù)功能發(fā)生變化,比如:原本部門經(jīng)理審批聚餐費(fèi)用的時(shí)候,只是判斷是否批準(zhǔn);現(xiàn)在,部門經(jīng)理可能在審批聚餐費(fèi)用的時(shí)候,核算本部門的實(shí)時(shí)成本,這就出現(xiàn)新的業(yè)務(wù)處理功能了。

如果采用上面的實(shí)現(xiàn),要是處理的邏輯發(fā)生了變化,解決的方法,一個(gè)是生成一個(gè)子類,覆蓋requestToProjectManager方法,然后在里面實(shí)現(xiàn)新的處理;另外一個(gè)方法就是修改處理申請(qǐng)的方法的源代碼來實(shí)現(xiàn)。要是具體處理環(huán)節(jié)的業(yè)務(wù)處理的功能發(fā)生了變化,那就只好找到相應(yīng)的處理方法,進(jìn)行源代碼修改了。

總之都不是什么好方法,也就是說,如果出現(xiàn)聚餐費(fèi)用申請(qǐng)的處理流程變化的情況,或者是出現(xiàn)各個(gè)處理環(huán)節(jié)的功能變化的時(shí)候,上面的實(shí)現(xiàn)方式是很難靈活的變化來適應(yīng)新功能的要求的。

把上面的問題抽象一下:客戶端發(fā)出一個(gè)請(qǐng)求,會(huì)有很多對(duì)象都可以來處理這個(gè)請(qǐng)求,而且不同對(duì)象的處理邏輯是不一樣的。對(duì)于客戶端而言,無所謂誰來處理,反正有對(duì)象處理就可以了。

而且在上述處理中,還希望處理流程是可以靈活變動(dòng)的,而處理請(qǐng)求的對(duì)象需要能方便的修改或者是被替換掉,以適應(yīng)新的業(yè)務(wù)功能的需要。

請(qǐng)問如何才能實(shí)現(xiàn)上述要求?

2 解決方案#

2.1 職責(zé)鏈模式來解決##

用來解決上述問題的一個(gè)合理的解決方案,就是使用職責(zé)鏈模式。那么什么是職責(zé)鏈模式呢?

  1. 職責(zé)鏈模式定義
**職責(zé)鏈模式定義**
  1. 應(yīng)用職責(zé)鏈模式來解決的思路

仔細(xì)分析上面的場景,當(dāng)客戶端提出一個(gè)聚餐費(fèi)用的申請(qǐng),后續(xù)處理這個(gè)申請(qǐng)的對(duì)象,項(xiàng)目經(jīng)理、部門經(jīng)理和總經(jīng)理,自然的形成了一個(gè)鏈,從項(xiàng)目經(jīng)理-部門經(jīng)理-總經(jīng)理,客戶端的申請(qǐng)請(qǐng)求就在這個(gè)鏈中傳遞,直到有領(lǐng)導(dǎo)處理為止??雌饋?,上面的功能要求很適合采用職責(zé)鏈來處理這個(gè)業(yè)務(wù)。

要想讓處理請(qǐng)求的流程可以靈活的變動(dòng),一個(gè)基本的思路,那就是動(dòng)態(tài)構(gòu)建流程步驟,這樣隨時(shí)都可以重新組合出新的流程來。而要讓處理請(qǐng)求的對(duì)象也要很靈活,那就要讓它足夠簡單,最好是只實(shí)現(xiàn)單一的功能,或者是有限的功能,這樣更有利于修改和復(fù)用。

職責(zé)鏈模式就很好的體現(xiàn)了上述的基本思路,首先職責(zé)鏈模式會(huì)定義一個(gè)所有處理請(qǐng)求的對(duì)象都要繼承實(shí)現(xiàn)的抽象類,這樣就有利于隨時(shí)切換新的實(shí)現(xiàn);其次每個(gè)處理請(qǐng)求對(duì)象只實(shí)現(xiàn)業(yè)務(wù)流程中的一步業(yè)務(wù)處理,這樣使其變得簡單;最后職責(zé)鏈模式會(huì)動(dòng)態(tài)的來組合這些處理請(qǐng)求的對(duì)象,把它們按照流程動(dòng)態(tài)組合起來,并要求它們依次調(diào)用,這樣就動(dòng)態(tài)的實(shí)現(xiàn)了流程。

這樣一來,如果流程發(fā)生了變化,只要重新組合就好了;如果某個(gè)處理的業(yè)務(wù)功能發(fā)生了變化,一個(gè)方案是修改該處理對(duì)應(yīng)的處理對(duì)象,另一個(gè)方案是直接提供一個(gè)新的實(shí)現(xiàn),然后在組合流程的時(shí)候,用新的實(shí)現(xiàn)替換掉舊的實(shí)現(xiàn)就可以了。

2.2 模式結(jié)構(gòu)和說明##

職責(zé)鏈模式的結(jié)構(gòu)如圖所示:

職責(zé)鏈模式結(jié)構(gòu)圖

Handler:定義職責(zé)的接口,通常在這里定義處理請(qǐng)求的方法,可以在這里實(shí)現(xiàn)后繼鏈。

ConcreteHandler:實(shí)現(xiàn)職責(zé)的類,在這個(gè)類里面,實(shí)現(xiàn)對(duì)在它職責(zé)范圍內(nèi)請(qǐng)求的處理,如果不處理,就繼續(xù)轉(zhuǎn)發(fā)請(qǐng)求給后繼者。

Client:職責(zé)鏈的客戶端,向鏈上的具體處理者對(duì)象提交請(qǐng)求,讓職責(zé)鏈負(fù)責(zé)處理。

2.3 職責(zé)鏈模式示例代碼##

  1. 先來看看職責(zé)的接口定義,示例代碼如下:
/**
   * 職責(zé)的接口,也就是處理請(qǐng)求的接口
   */
public abstract class Handler {
    /**
     * 持有后繼的職責(zé)對(duì)象
     */
    protected Handler successor;
    /**
     * 設(shè)置后繼的職責(zé)對(duì)象
     * @param successor 后繼的職責(zé)對(duì)象
     */
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    /**
     * 示意處理請(qǐng)求的方法,雖然這個(gè)示意方法是沒有傳入?yún)?shù),
     * 但實(shí)際是可以傳入?yún)?shù)的,根據(jù)具體需要來選擇是否傳遞參數(shù)
     */
    public abstract void handleRequest();
}
  1. 接下來看看具體的職責(zé)實(shí)現(xiàn)對(duì)象,示例代碼如下:
/**
   * 具體的職責(zé)對(duì)象,用來處理請(qǐng)求
   */
public class ConcreteHandler1 extends Handler {
    public void handleRequest() {
        //根據(jù)某些條件來判斷是否屬于自己處理的職責(zé)范圍
        //判斷條件比如:從外部傳入的參數(shù),或者這里主動(dòng)去獲取的外部數(shù)據(jù),
        //如從數(shù)據(jù)庫中獲取等,下面這句話只是個(gè)示意
        boolean someCondition = false;
     
        if(someCondition){
            //如果屬于自己處理的職責(zé)范圍,就在這里處理請(qǐng)求
            //具體的處理代碼
            System.out.println("ConcreteHandler1 handle request");
        }else{
            //如果不屬于自己處理的職責(zé)范圍,那就判斷是否還有后繼的職責(zé)對(duì)象
            //如果有,就轉(zhuǎn)發(fā)請(qǐng)求給后繼的職責(zé)對(duì)象
            //如果沒有,什么都不做,自然結(jié)束
            if(this.successor!=null){
                this.successor.handleRequest();
            }
        }
    }
}

另外一個(gè)ConcreteHandler2和上面ConcreteHandler1的示意代碼幾乎是一樣的,因此就不去贅述了。

  1. 接下來看看客戶端的示意,示例代碼如下:
/**
   * 職責(zé)鏈的客戶端,這里只是個(gè)示意
   */
public class Client {
    public static void main(String[] args) {
        //先要組裝職責(zé)鏈
        Handler h1 = new ConcreteHandler1();
        Handler h2 = new ConcreteHandler2();
     
        h1.setSuccessor(h2);    
        //然后提交請(qǐng)求
        h1.handleRequest();
    }
}

2.4 使用職責(zé)鏈模式重寫示例##

要使用職責(zé)鏈模式來重寫示例,還是先來實(shí)現(xiàn)如下的功能:當(dāng)某人提出聚餐費(fèi)用申請(qǐng)的請(qǐng)求后,該請(qǐng)求會(huì)在項(xiàng)目經(jīng)理-部門經(jīng)理-總經(jīng)理這樣一條領(lǐng)導(dǎo)處理鏈上進(jìn)行傳遞,發(fā)出請(qǐng)求的人并不知道誰會(huì)來處理他的請(qǐng)求,每個(gè)領(lǐng)導(dǎo)會(huì)根據(jù)自己的職責(zé)范圍,來判斷是處理請(qǐng)求還是把請(qǐng)求交給更高級(jí)的領(lǐng)導(dǎo),只要有領(lǐng)導(dǎo)處理了,傳遞就結(jié)束了。

需要把每位領(lǐng)導(dǎo)的處理獨(dú)立出來,實(shí)現(xiàn)成單獨(dú)的職責(zé)處理對(duì)象,然后為它們提供一個(gè)公共的、抽象的父職責(zé)對(duì)象,這樣就可以在客戶端來動(dòng)態(tài)的組合職責(zé)鏈,實(shí)現(xiàn)不同的功能要求了。還是看一下示例的整體結(jié)構(gòu),會(huì)有助于對(duì)示例的理解和把握。如圖所示:

使用職責(zé)鏈模式的示例程序的結(jié)構(gòu)示意圖
  1. 定義職責(zé)的抽象類

首先來看看定義所有職責(zé)的抽象類,也就是所有職責(zé)的外觀,在這個(gè)類里面持有下一個(gè)處理請(qǐng)求的對(duì)象,同時(shí)還要定義業(yè)務(wù)處理方法,示例代碼如下:

/**
 * 定義職責(zé)對(duì)象的接口
 */
public abstract class Handler {
    /**
     * 持有下一個(gè)處理請(qǐng)求的對(duì)象
     */
    protected Handler successor = null;
    /**
     * 設(shè)置下一個(gè)處理請(qǐng)求的對(duì)象
     * @param successor 下一個(gè)處理請(qǐng)求的對(duì)象
     */
    public void setSuccessor(Handler successor){
       this.successor = successor;
    }
    /**
     * 處理聚餐費(fèi)用的申請(qǐng)
     * @param user 申請(qǐng)人
     * @param fee 申請(qǐng)的錢數(shù)
     * @return 成功或失敗的具體通知
     */
    public abstract String handleFeeRequest(String user,double fee);
}
  1. 實(shí)現(xiàn)各自的職責(zé)

現(xiàn)在實(shí)現(xiàn)的處理聚餐費(fèi)用流程是:申請(qǐng)人提出的申請(qǐng)交給項(xiàng)目經(jīng)理處理,項(xiàng)目經(jīng)理的處理權(quán)限是500元以內(nèi),超過500元,把申請(qǐng)轉(zhuǎn)給部門經(jīng)理處理,部門經(jīng)理的處理權(quán)限是1000元以內(nèi),超過1000元,把申請(qǐng)轉(zhuǎn)給總經(jīng)理處理。

分析上述流程,對(duì)請(qǐng)求主要有三個(gè)處理環(huán)節(jié),把它們分別實(shí)現(xiàn)成為職責(zé)對(duì)象,一個(gè)對(duì)象實(shí)現(xiàn)一個(gè)環(huán)節(jié)的處理功能,這樣就會(huì)比較簡單。

先看看項(xiàng)目經(jīng)理的處理吧,示例代碼如下:

public class ProjectManager extends Handler{
    public String handleFeeRequest(String user, double fee) {
        String str = "";
        //項(xiàng)目經(jīng)理的權(quán)限比較小,只能在500以內(nèi)
        if(fee < 500){
            //為了測試,簡單點(diǎn),只同意小李的
            if("小李".equals(user)){
                str = "項(xiàng)目經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }else{
                //其它人一律不同意
                str = "項(xiàng)目經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }
            return str;
        }else{
            //超過500,繼續(xù)傳遞給級(jí)別更高的人處理
            if(this.successor!=null){
                return successor.handleFeeRequest(user, fee);
            }
        }
        return str;
    }
}

接下來看看部門經(jīng)理的處理,示例代碼如下:

public class DepManager extends Handler{ 
    public String handleFeeRequest(String user, double fee) {
        String str = "";
        //部門經(jīng)理的權(quán)限只能在1000以內(nèi)
        if(fee < 1000){
            //為了測試,簡單點(diǎn),只同意小李申請(qǐng)的
            if("小李".equals(user)){
                str = "部門經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }else{
                //其它人一律不同意
                str = "部門經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }
            return str;
        }else{
            //超過1000,繼續(xù)傳遞給級(jí)別更高的人處理
            if(this.successor!=null){
                return this.successor.handleFeeRequest(user, fee);
            }
        }
        return str;
    }
}

再看總經(jīng)理的處理,示例代碼如下:

public class GeneralManager extends Handler{
    public String handleFeeRequest(String user, double fee) {
        String str = "";
        //總經(jīng)理的權(quán)限很大,只要請(qǐng)求到了這里,他都可以處理
        if(fee >= 1000){
            //為了測試,簡單點(diǎn),只同意小李的
            if("小李".equals(user)){
                str = "總經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }else{
                //其它人一律不同意
                str = "總經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }
            return str;
        }else{
            //如果還有后繼的處理對(duì)象,繼續(xù)傳遞
            if(this.successor!=null){
                return successor.handleFeeRequest(user, fee);
            }
        }
        return str;
    }
}
  1. 使用職責(zé)鏈

那么客戶端如何使用職責(zé)鏈呢,最重要的就是要先構(gòu)建職責(zé)鏈,然后才能使用。示例代碼如下:

public class Client {
    public static void main(String[] args) {
        //先要組裝職責(zé)鏈   
        Handler h1 = new GeneralManager();
        Handler h2 = new DepManager();
        Handler h3 = new ProjectManager();
        h3.setSuccessor(h2);
        h2.setSuccessor(h1);
     
        //開始測試
        String ret1 = h3.handleFeeRequest("小李", 300);
        System.out.println("the ret1="+ret1); 
        String ret2 = h3.handleFeeRequest("小張", 300);
        System.out.println("the ret2="+ret2); 
     
        String ret3 = h3.handleFeeRequest("小李", 600);
        System.out.println("the ret3="+ret3); 
        String ret4 = h3.handleFeeRequest("小張", 600);
        System.out.println("the ret4="+ret4); 
     
        String ret5 = h3.handleFeeRequest("小李", 1200); 
        System.out.println("the ret5="+ret5); 
        String ret6 = h3.handleFeeRequest("小張", 1200);
        System.out.println("the ret6="+ret6); 
    }
}

運(yùn)行結(jié)果如下:

the ret1=項(xiàng)目經(jīng)理同意小李聚餐費(fèi)用300.0元的請(qǐng)求
the ret2=項(xiàng)目經(jīng)理不同意小張聚餐費(fèi)用300.0元的請(qǐng)求
the ret3=部門經(jīng)理同意小李聚餐費(fèi)用600.0元的請(qǐng)求
the ret4=部門經(jīng)理不同意小張聚餐費(fèi)用600.0元的請(qǐng)求
the ret5=總經(jīng)理同意小李聚餐費(fèi)用1200.0元的請(qǐng)求
the ret6=總經(jīng)理不同意小張聚餐費(fèi)用1200.0元的請(qǐng)求

看起來結(jié)果跟前面不用模式的實(shí)現(xiàn)方案的運(yùn)行結(jié)果是一樣的,它們本來就是實(shí)現(xiàn)的同樣的功能,只不過實(shí)現(xiàn)方式不同而已。

  1. 如何運(yùn)行

理解了示例的整體結(jié)構(gòu)和具體實(shí)現(xiàn),那么示例的具體運(yùn)行過程是怎樣的呢?

下面就以“小李申請(qǐng)聚餐費(fèi)用1200元”這個(gè)費(fèi)用申請(qǐng)為例來說明,調(diào)用過程的示意圖如圖所示:

職責(zé)鏈?zhǔn)纠{(diào)用過程示意圖

3 模式講解#

3.1 認(rèn)識(shí)職責(zé)鏈模式##

  1. 模式功能

職責(zé)鏈模式主要用來處理:“客戶端發(fā)出一個(gè)請(qǐng)求,有多個(gè)對(duì)象都有機(jī)會(huì)來處理這一個(gè)請(qǐng)求,但是客戶端不知道究竟誰會(huì)來處理他的請(qǐng)求”,這樣的情況。也就是需要讓請(qǐng)求者和接收者解耦,這樣就可以動(dòng)態(tài)的切換和組合接收者了。

要注意在標(biāo)準(zhǔn)的職責(zé)鏈模式里面,是只要沒有有對(duì)象處理了請(qǐng)求,這個(gè)請(qǐng)求就到此為止,不再被傳遞和處理了。

如果是要變形使用職責(zé)鏈,就可以讓這個(gè)請(qǐng)求繼續(xù)傳遞,每個(gè)職責(zé)對(duì)象對(duì)這個(gè)請(qǐng)求進(jìn)行一定的功能處理,從而形成一個(gè)處理請(qǐng)求的功能鏈。

  1. 隱式接收者

當(dāng)客戶端發(fā)出請(qǐng)求的時(shí)候,客戶端并不知道誰會(huì)真正處理他的請(qǐng)求,客戶端只知道他提交請(qǐng)求的第一個(gè)對(duì)象。從第一個(gè)處理對(duì)象開始,整個(gè)職責(zé)鏈里面的對(duì)象,要么自己處理請(qǐng)求,要么繼續(xù)轉(zhuǎn)發(fā)給下一個(gè)接收者。

也就是對(duì)于請(qǐng)求者而言,并不知道最終的接收者是誰,但是一般情況下,總是會(huì)有一個(gè)對(duì)象來處理的,因此稱為隱式接收者。

  1. 如何構(gòu)建鏈

職責(zé)鏈的鏈怎么構(gòu)建呢?這是個(gè)大問題,實(shí)現(xiàn)的方式也是五花八門,歸結(jié)起來大致有以下一些方式。

首先是按照實(shí)現(xiàn)的地方來說:

可以實(shí)現(xiàn)在客戶端,在提交請(qǐng)求前組合鏈,也就是在使用的時(shí)候動(dòng)態(tài)組合鏈,稱為外部鏈;

也可以在Handler里面實(shí)現(xiàn)鏈的組合,算是內(nèi)部鏈的一種;

當(dāng)然還有一種就是在各個(gè)職責(zé)對(duì)象里面,由各個(gè)職責(zé)對(duì)象自行決定后續(xù)的處理對(duì)象,這種實(shí)現(xiàn)方式要求每個(gè)職責(zé)對(duì)象除了進(jìn)行業(yè)務(wù)處理外,還必須了解整個(gè)業(yè)務(wù)流程。

按照構(gòu)建鏈的數(shù)據(jù)來源,也就是決定了按照什么順序來組合鏈的數(shù)據(jù),又分為幾種:

一種就是在程序里面動(dòng)態(tài)組合;

也可以通過外部,比如數(shù)據(jù)庫來獲取組合的數(shù)據(jù),這種屬于數(shù)據(jù)庫驅(qū)動(dòng)的方式;

還有一種方式就是通過配置文件傳遞進(jìn)來,也可以是流程的配置文件;

如果是從外部獲取數(shù)據(jù)來構(gòu)建鏈,那么在程序運(yùn)行的時(shí)候,會(huì)讀取這些數(shù)據(jù),然后根據(jù)數(shù)據(jù)的要求來獲取相應(yīng)的對(duì)象,并組合起來。

還有一種是不需要構(gòu)建鏈,因?yàn)橐延械膶?duì)象已經(jīng)自然構(gòu)成鏈了,這種情況多出現(xiàn)在組合模式構(gòu)建的對(duì)象樹中,這樣子對(duì)象可以很自然的向上找到自己的父對(duì)象。就像部門人員的組織結(jié)構(gòu)一樣,頂層是總經(jīng)理,總經(jīng)理下面是各個(gè)部門的經(jīng)理,部門經(jīng)理下面是項(xiàng)目經(jīng)理,項(xiàng)目經(jīng)理下面是各個(gè)普通員工,自然就可以形成:普通員工-項(xiàng)目經(jīng)理-部門經(jīng)理-總經(jīng)理這樣的鏈。

  1. 誰來處理

職責(zé)鏈中那么多處理對(duì)象,到底誰來處理請(qǐng)求呢,這個(gè)是在運(yùn)行時(shí)期動(dòng)態(tài)決定的。當(dāng)請(qǐng)求被傳遞到某個(gè)處理對(duì)象的時(shí)候,這個(gè)對(duì)象會(huì)按照已經(jīng)設(shè)定好的條件來判斷,是否屬于自己處理的范圍,如果是就處理,如果不是就轉(zhuǎn)發(fā)請(qǐng)求給下一個(gè)對(duì)象。

  1. 請(qǐng)求一定會(huì)被處理嗎

在職責(zé)鏈模式中,請(qǐng)求不一定會(huì)被處理,因?yàn)榭赡軟]有合適的處理者,請(qǐng)求在職責(zé)鏈里面從頭傳遞到尾,每個(gè)處理對(duì)象都判斷不屬于自己處理,最后請(qǐng)求就沒有對(duì)象來處理。這一點(diǎn)是需要注意的。

可以在職責(zé)鏈的末端始終加上一個(gè)不支持此功能處理的職責(zé)對(duì)象,這樣如果傳遞到這里,就會(huì)出現(xiàn)提示,本職責(zé)鏈沒有對(duì)象處理這個(gè)請(qǐng)求。

3.2 處理多種請(qǐng)求##

前面的示例都是同一個(gè)職責(zé)鏈處理一種請(qǐng)求的情況,現(xiàn)在有這樣的需求,還是費(fèi)用申請(qǐng)的功能,這次是申請(qǐng)預(yù)支差旅費(fèi),假設(shè)還是同一流程,也就是組合同一個(gè)職責(zé)鏈,從項(xiàng)目經(jīng)理-傳遞給部門經(jīng)理-傳遞給總經(jīng)理,雖然流程相同,但是每個(gè)處理類需要處理兩種請(qǐng)求,它們的具體業(yè)務(wù)邏輯是不一樣的,那么該如何實(shí)現(xiàn)呢?

  1. 簡單的處理方式

要解決這個(gè)問題,也不是很困難,一個(gè)簡單的方法就是為每種業(yè)務(wù)單獨(dú)定義一個(gè)方法,然后客戶端根據(jù)不同的需要調(diào)用不同的方法,還是通過代碼來示例一下。注意這里故意的把兩個(gè)方法做的有些不一樣,一個(gè)是返回String類型的值,一個(gè)是返回boolean類型的值;另外一個(gè)是返回到客戶端再輸出信息,一個(gè)是直接在職責(zé)處理里面就輸出信息

(1)首先是改造職責(zé)對(duì)象的接口,添加上新的業(yè)務(wù)方法,示例代碼如下:

/**
 * 定義職責(zé)對(duì)象的接口
 */
public abstract class Handler {
    /**
     * 持有下一個(gè)處理請(qǐng)求的對(duì)象
     */
    protected Handler successor = null;
    /**
     * 設(shè)置下一個(gè)處理請(qǐng)求的對(duì)象
     * @param successor 下一個(gè)處理請(qǐng)求的對(duì)象
     */
    public void setSuccessor(Handler successor){
        this.successor = successor;
    }
    /**
     * 處理聚餐費(fèi)用的申請(qǐng)
     * @param user 申請(qǐng)人
     * @param fee 申請(qǐng)的錢數(shù)
     * @return 成功或失敗的具體通知
     */
    public abstract String handleFeeRequest(String user,double fee);
    /**
     * 處理預(yù)支差旅費(fèi)用的申請(qǐng)
     * @param user 申請(qǐng)人
     * @param requestFee 申請(qǐng)的錢數(shù)
     * @return 是否同意
     */
    public abstract boolean handlePreFeeRequest(String user,double requestFee);
}

(2)職責(zé)的接口發(fā)生了改變,對(duì)應(yīng)的處理類也要改變,這幾個(gè)處理類是類似的,原有的功能不變,然后在新的實(shí)現(xiàn)方法里面,同樣判斷一下是否屬于自己處理的范圍,如果屬于自己處理的范圍那就處理,否則就傳遞到下一個(gè)處理。還是示范一個(gè),看看項(xiàng)目經(jīng)理的處理吧,示例代碼如下:

public class ProjectManager extends Handler{
    public String handleFeeRequest(String user, double fee) {
        String str = "";
        //項(xiàng)目經(jīng)理的權(quán)限比較小,只能在500以內(nèi)
        if(fee < 500){
            //為了測試,簡單點(diǎn),只同意小李的
            if("小李".equals(user)){
                str = "項(xiàng)目經(jīng)理同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }else{
               //其它人一律不同意
               str = "項(xiàng)目經(jīng)理不同意"+user+"聚餐費(fèi)用"+fee+"元的請(qǐng)求";
            }
            return str;
        }else{
            //超過500,繼續(xù)傳遞給級(jí)別更高的人處理
            if(this.successor!=null){
                return successor.handleFeeRequest(user, fee);
            }
        }
        return str;
    }
    public boolean handlePreFeeRequest(String user, double requestNum) {
        //項(xiàng)目經(jīng)理的權(quán)限比較小,只能在5000以內(nèi)
        if(requestNum < 5000){
            //工作需要嘛,統(tǒng)統(tǒng)同意
            System.out.println("項(xiàng)目經(jīng)理同意"+user+"預(yù)支差旅費(fèi)用"+requestNum+"元的請(qǐng)求");
            return true;
        }else{
            //超過5000,繼續(xù)傳遞給級(jí)別更高的人處理
            if(this.successor!=null){
                return this.successor.handlePreFeeRequest(user, requestNum);
            }
        }
        return false;
    }
}  

其它的處理類似,就不去演示了。

(3)準(zhǔn)備好了各個(gè)處理職責(zé)的類,看看客戶端如何調(diào)用,示例代碼如下:

public class Client {
    public static void main(String[] args) {
        //先要組裝職責(zé)鏈   
        Handler h1 = new GeneralManager();
        Handler h2 = new DepManager();
        Handler h3 = new ProjectManager();
        h3.setSuccessor(h2);
        h2.setSuccessor(h1);
  
        //開始測試申請(qǐng)聚餐費(fèi)用
        String ret1 = h3.handleFeeRequest("小李", 300);
        System.out.println("the ret1="+ret1);
        String ret2 = h3.handleFeeRequest("小李", 600);
        System.out.println("the ret2="+ret2);
        String ret3 = h3.handleFeeRequest("小李", 1200);
        System.out.println("the ret3="+ret3);
     
        //開始測試申請(qǐng)差旅費(fèi)用
        h3.handlePreFeeRequest("小張", 3000);
        h3.handlePreFeeRequest("小張", 6000);
        h3.handlePreFeeRequest("小張", 32000);
   }
}

運(yùn)行的結(jié)果如下:

the ret1=項(xiàng)目經(jīng)理同意小李聚餐費(fèi)用300.0元的請(qǐng)求
the ret2=部門經(jīng)理同意小李聚餐費(fèi)用600.0元的請(qǐng)求
the ret3=總經(jīng)理同意小李聚餐費(fèi)用1200.0元的請(qǐng)求
項(xiàng)目經(jīng)理同意小張預(yù)支差旅費(fèi)用3000.0元的請(qǐng)求
部門經(jīng)理同意小張預(yù)支差旅費(fèi)用6000.0元的請(qǐng)求
總經(jīng)理同意小張預(yù)支差旅費(fèi)用32000.0元的請(qǐng)求
  1. 通用請(qǐng)求的處理方式

上面的實(shí)現(xiàn)看起來很容易,但是仔細(xì)想想,這樣實(shí)現(xiàn)有沒有什么問題呢?

這種實(shí)現(xiàn)方式有一個(gè)很明顯的問題,那就是只要增加一個(gè)業(yè)務(wù),就需要修改職責(zé)的接口,這是很不靈活的,Java開發(fā)中很強(qiáng)調(diào)面向接口編程,因此接口應(yīng)該相對(duì)保持穩(wěn)定,接口一改,需要修改的地方就太多了,頻繁修改接口絕對(duì)不是個(gè)好主意。

那有沒有什么好方法來實(shí)現(xiàn)呢?分析一下現(xiàn)在變化的東西:

一是不同的業(yè)務(wù)需要傳遞的業(yè)務(wù)數(shù)據(jù)不同;

二是不同的業(yè)務(wù)請(qǐng)求的方法不同;

三是不同的職責(zé)對(duì)象處理這些不同的業(yè)務(wù)請(qǐng)求的業(yè)務(wù)邏輯不同;

現(xiàn)在有一種簡單的方式,可以較好的解決這些問題。首先定義一套通用的調(diào)用框架,用一個(gè)通用的請(qǐng)求對(duì)象來封裝請(qǐng)求傳遞的參數(shù);然后定義一個(gè)通用的調(diào)用方法,這個(gè)方法不去區(qū)分具體業(yè)務(wù),所有的業(yè)務(wù)都是這一個(gè)方法,那么具體的業(yè)務(wù)如何區(qū)分呢,就是在通用的請(qǐng)求對(duì)象里面會(huì)有一個(gè)業(yè)務(wù)的標(biāo)記;到了職責(zé)對(duì)象里面,愿意處理就跟原來一樣的處理方式,如果不愿意處理,就傳遞到下一個(gè)處理對(duì)象就好了。

對(duì)于返回值也可以來個(gè)通用的,最簡單的就是使用Object類型。

看例子吧,為了示范,先就假定只有一個(gè)業(yè)務(wù)方法,等把這一個(gè)方法搞定了,明白了,然后再擴(kuò)展一個(gè)業(yè)務(wù)方法,就能清晰地看出這種設(shè)計(jì)的好處了。

(1)先看看通用的請(qǐng)求對(duì)象的定義,示例代碼如下:

/**
 * 通用的請(qǐng)求對(duì)象
 */
public class RequestModel {
    /**
     * 表示具體的業(yè)務(wù)類型
     */
    private String type;
    /**
     * 通過構(gòu)造方法把具體的業(yè)務(wù)類型傳遞進(jìn)來
     * @param type 具體的業(yè)務(wù)類型
     */
    public RequestModel(String type){
        this.type = type;
    }
    public String getType() {
        return type;
    }  
}

(2)看看此時(shí)的通用職責(zé)處理對(duì)象,在這里要實(shí)現(xiàn)一個(gè)通用的調(diào)用框架,示例代碼如下:

/**
 * 定義職責(zé)對(duì)象的接口
 */
public abstract class Handler {
    /**
     * 持有下一個(gè)處理請(qǐng)求的對(duì)象
     */
    protected Handler successor = null;
    /**
     * 設(shè)置下一個(gè)處理請(qǐng)求的對(duì)象
     * @param successor 下一個(gè)處理請(qǐng)求的對(duì)象
     */
    public void setSuccessor(Handler successor){
        this.successor = successor;
    }
    /**
     * 通用的請(qǐng)求處理方法
     * @param rm 通用的請(qǐng)求對(duì)象
     * @return 處理后需要返回的對(duì)象
     */
    public Object handleRequest(RequestModel rm){
        if(successor != null){
            //這個(gè)是默認(rèn)的實(shí)現(xiàn),如果子類不愿意處理這個(gè)請(qǐng)求,
            //那就傳遞到下一個(gè)職責(zé)對(duì)象去處理
            return this.successor.handleRequest(rm);
        }else{
            System.out.println("沒有后續(xù)處理或者暫時(shí)不支持這樣的功能處理");
            return false;
        }
    }
}

(3)現(xiàn)在來加上第一個(gè)業(yè)務(wù),就是“聚餐費(fèi)用申請(qǐng)”的處理,為了描述具體的業(yè)務(wù)數(shù)據(jù),需要擴(kuò)展通用的請(qǐng)求對(duì)象,把業(yè)務(wù)數(shù)據(jù)封裝進(jìn)去,另外定義一個(gè)請(qǐng)求對(duì)象,示例代碼如下:

/**
 * 封裝跟聚餐費(fèi)用申請(qǐng)業(yè)務(wù)相關(guān)的請(qǐng)求數(shù)據(jù)
 */
public class FeeRequestModel extends RequestModel{
    /**
     * 約定具體的業(yè)務(wù)類型
     */
    public final static String FEE_TYPE = "fee";
    public FeeRequestModel() {
        super(FEE_TYPE);
    }
    /**
     * 申請(qǐng)人
     */
    private String user;
    /**
     * 申請(qǐng)金額
     */
    private double fee;
    public String getUser() {
        return user;
    }
    public void setUser(String user) {
        this.user = user;
    }
    public double getFee() {
        return fee;
    }
    public void setFee(double fee) {
        this.fee = fee;
    }
}

(4)接下來該實(shí)現(xiàn)職責(zé)對(duì)象的處理了,大同小異的,還是看一個(gè)就行了,看看項(xiàng)目經(jīng)理的處理吧,在這個(gè)處理類里面,首先要覆蓋父類的通用業(yè)務(wù)處理方法,然后在里面處理自己想要實(shí)現(xiàn)的業(yè)務(wù),不想處理的就讓父類去處理,父類會(huì)默認(rèn)的傳遞給下一個(gè)處理對(duì)象,示例代碼如下:

/**
 * 實(shí)現(xiàn)項(xiàng)目經(jīng)理處理聚餐費(fèi)用申請(qǐng)的對(duì)象
 */
public class ProjectManager extends Handler{
    public Object handleRequest(RequestModel rm){
        if(FeeRequestModel.FEE_TYPE.equals(rm.getType())){
            //表示聚餐費(fèi)用申請(qǐng)
            return handleFeeRequest(rm);
        }else{
            //其它的項(xiàng)目經(jīng)理暫時(shí)不想處理
            return super.handleRequest(rm);
        }
    }
    private Object handleFeeRequest(RequestModel rm) {
        //先把通用的對(duì)象造型回來
        FeeRequestModel frm = (FeeRequestModel)rm;
        String str = "";
        //項(xiàng)目經(jīng)理的權(quán)限比較小,只能在500以內(nèi)
        if(frm.getFee() < 500){
            //為了測試,簡單點(diǎn),只同意小李的
            if("小李".equals(frm.getUser())){
                str = "項(xiàng)目經(jīng)理同意"+frm.getUser()+"聚餐費(fèi)用"+frm.getFee()+"元的請(qǐng)求";
            }else{
                //其它人一律不同意
                str = "項(xiàng)目經(jīng)理不同意"+frm.getUser()+"聚餐費(fèi)用"+frm.getFee()+"元的請(qǐng)求";
            }
            return str;
        }else{
            //超過500,繼續(xù)傳遞給級(jí)別更高的人處理
            if(this.successor!=null){
                return successor.handleRequest(rm);
            }
        }
        return str;
    }
}

部門經(jīng)理、總經(jīng)理的處理對(duì)象和項(xiàng)目經(jīng)理的處理類似,就不去示例了。

(5)客戶端也需要變化,對(duì)于客戶端,唯一的麻煩是需要知道每個(gè)業(yè)務(wù)對(duì)應(yīng)的具體的請(qǐng)求對(duì)象,因?yàn)橐庋b業(yè)務(wù)數(shù)據(jù)進(jìn)去,示例代碼如下:

public class Client {
    public static void main(String[] args) {
        //先要組裝職責(zé)鏈   
        Handler h1 = new GeneralManager();
        Handler h2 = new DepManager();
        Handler h3 = new ProjectManager();
        h3.setSuccessor(h2);
        h2.setSuccessor(h1);
     
        //開始測試申請(qǐng)聚餐費(fèi)用
        FeeRequestModel frm = new FeeRequestModel();
        frm.setFee(300);
        frm.setUser("小李");
        //調(diào)用處理
        String ret1 = (String)h3.handleRequest(frm);
        System.out.println("ret1="+ret1);
     
        //重新設(shè)置申請(qǐng)金額,再調(diào)用處理
        frm.setFee(800);    
        h3.handleRequest(frm);
        String ret2 = (String)h3.handleRequest(frm);
        System.out.println("ret2="+ret2);
     
        //重新設(shè)置申請(qǐng)金額,再調(diào)用處理
        frm.setFee(1600);   
        h3.handleRequest(frm);
        String ret3 = (String)h3.handleRequest(frm);
        System.out.println("ret3="+ret3);
    }
}

運(yùn)行結(jié)果如下:

ret1=項(xiàng)目經(jīng)理同意小李聚餐費(fèi)用300.0元的請(qǐng)求
ret2=部門經(jīng)理同意小李聚餐費(fèi)用800.0元的請(qǐng)求
ret3=總經(jīng)理同意小李聚餐費(fèi)用1600.0元的請(qǐng)求

(6)接下來看看如何在不改動(dòng)現(xiàn)有的框架的前提下,擴(kuò)展新的業(yè)務(wù),這樣才能說明這種設(shè)計(jì)的靈活性。

假如就是要實(shí)現(xiàn)上面示例過的另外一個(gè)功能“預(yù)支差旅費(fèi)申請(qǐng)”吧。要想擴(kuò)展新的業(yè)務(wù),第一步就是新建一個(gè)封裝業(yè)務(wù)數(shù)據(jù)的對(duì)象,示例代碼如下:

/**
 * 封裝跟預(yù)支差旅費(fèi)申請(qǐng)業(yè)務(wù)相關(guān)的請(qǐng)求數(shù)據(jù)
 */
public class PreFeeRequestModel extends RequestModel{
    /**
     * 約定具體的業(yè)務(wù)類型
     */
    public final static String FEE_TYPE = "preFee";
    public PreFeeRequestModel() {
        super(FEE_TYPE);
    }
    /**
     * 申請(qǐng)人
     */
    private String user;
    /**
     * 申請(qǐng)金額
     */
    private double fee;
    public String getUser() {
        return user;
    }
    public void setUser(String user) {
        this.user = user;
    }
    public double getFee() {
        return fee;
    }
    public void setFee(double fee) {
        this.fee = fee;
    }
}

有些朋友會(huì)發(fā)現(xiàn),這個(gè)對(duì)象跟封裝聚餐費(fèi)用申請(qǐng)業(yè)務(wù)數(shù)據(jù)的對(duì)象幾乎完全一樣的,這里要說明一下,一樣的原因主要是我們?yōu)榱搜菔竞唵危O(shè)計(jì)得相似,實(shí)際業(yè)務(wù)中可能是不一樣的,因此,最好還是一個(gè)業(yè)務(wù)一個(gè)對(duì)象,如果確實(shí)有公共的數(shù)據(jù),可以定義公共的父類,最好不要讓不同的業(yè)務(wù)使用統(tǒng)一個(gè)對(duì)象,容易混淆。

(7)對(duì)于具體進(jìn)行職責(zé)處理的類,比較好的方式就是擴(kuò)展出子類來,然后在子類里面實(shí)現(xiàn)新加入的業(yè)務(wù),當(dāng)然也可以直接在原來的對(duì)象上改。還是采用擴(kuò)展出子類的方式吧,還是看看新的項(xiàng)目經(jīng)理的處理類,示例代碼如下:

/**
 * 實(shí)現(xiàn)為項(xiàng)目經(jīng)理增加預(yù)支差旅費(fèi)用申請(qǐng)?zhí)幚淼墓δ艿淖訉?duì)象,
 * 現(xiàn)在的項(xiàng)目經(jīng)理既可以處理聚餐費(fèi)用申請(qǐng),又可以處理預(yù)支差旅費(fèi)用申請(qǐng)
 */
public class ProjectManager2 extends ProjectManager{
    public Object handleRequest(RequestModel rm){
        if(PreFeeRequestModel.FEE_TYPE.equals(rm.getType())){
            //表示預(yù)支差旅費(fèi)用申請(qǐng)
            return myHandler(rm);
        }else{
            //其它的讓父類去處理
            return super.handleRequest(rm);
        }
    }
    private Object myHandler(RequestModel rm) {
        //先把通用的對(duì)象造型回來
        PreFeeRequestModel frm = (PreFeeRequestModel)rm;
        //項(xiàng)目經(jīng)理的權(quán)限比較小,只能在5000以內(nèi)
        if(frm.getFee() < 5000){
            //工作需要嘛,統(tǒng)統(tǒng)同意
            System.out.println("項(xiàng)目經(jīng)理同意"+frm.getUser()+"預(yù)支差旅費(fèi)用"+frm.getFee()+"元的請(qǐng)求");
            return true;
        }else{
            //超過5000,繼續(xù)傳遞給級(jí)別更高的人處理
            if(this.successor!=null){
                return this.successor.handleRequest(rm);
            }
        }
        return false;
    }
}

部門經(jīng)理和總經(jīng)理的處理類似,就不去示例了。

(8)看看此時(shí)的測試,示例如下:

public class Client {
    public static void main(String[] args) {
        //先要組裝職責(zé)鏈   
        Handler h1 = new GeneralManager2();
        Handler h2 = new DepManager2();
        Handler h3 = new ProjectManager2();
        h3.setSuccessor(h2);
        h2.setSuccessor(h1);
     
        //開始測試申請(qǐng)聚餐費(fèi)用
        FeeRequestModel frm = new FeeRequestModel();
        frm.setFee(300);
        frm.setUser("小李");
        //調(diào)用處理
        String ret1 = (String)h3.handleRequest(frm);
        System.out.println("ret1="+ret1);
     
        //重新設(shè)置申請(qǐng)金額,再調(diào)用處理
        frm.setFee(800);    
        h3.handleRequest(frm);
        String ret2 = (String)h3.handleRequest(frm);
        System.out.println("ret2="+ret2);
     
        //重新設(shè)置申請(qǐng)金額,再調(diào)用處理
        frm.setFee(1600);   
        h3.handleRequest(frm);
        String ret3 = (String)h3.handleRequest(frm);
        System.out.println("ret3="+ret3);
     
        //開始測試申請(qǐng)預(yù)支差旅費(fèi)用
        PreFeeRequestModel pfrm = new PreFeeRequestModel();
        pfrm.setFee(3000);
        pfrm.setUser("小張");
        //調(diào)用處理
        h3.handleRequest(pfrm);
        //重新設(shè)置申請(qǐng)金額,再調(diào)用處理
        pfrm.setFee(6000);
        h3.handleRequest(pfrm);
        //重新設(shè)置申請(qǐng)金額,再調(diào)用處理
        pfrm.setFee(36000);
        h3.handleRequest(pfrm);
    }
}

運(yùn)行一下,試試看,運(yùn)行結(jié)果如下:

ret1=項(xiàng)目經(jīng)理同意小李聚餐費(fèi)用300.0元的請(qǐng)求
ret2=部門經(jīng)理同意小李聚餐費(fèi)用800.0元的請(qǐng)求
ret3=總經(jīng)理同意小李聚餐費(fèi)用1600.0元的請(qǐng)求
項(xiàng)目經(jīng)理同意小張預(yù)支差旅費(fèi)用3000.0元的請(qǐng)求
部門經(jīng)理同意小張預(yù)支差旅費(fèi)用6000.0元的請(qǐng)求
總經(jīng)理同意小張預(yù)支差旅費(fèi)用36000.0元的請(qǐng)求

好好體會(huì)一下這種設(shè)計(jì)方式的好處,相當(dāng)?shù)耐ㄓ煤挽`活,有了新業(yè)務(wù),只需要添加實(shí)現(xiàn)新功能的對(duì)象就可以了,但是帶來的缺陷就是可能會(huì)造成對(duì)象層次過多,或者出現(xiàn)較多的細(xì)粒度的對(duì)象,極端情況下,每次就擴(kuò)展一個(gè)方法,會(huì)出現(xiàn)大量只處理一個(gè)功能的細(xì)粒度對(duì)象

3.3 功能鏈##

在實(shí)際開發(fā)中,經(jīng)常會(huì)出現(xiàn)一個(gè)把職責(zé)鏈稍稍變形的用法。在標(biāo)準(zhǔn)的職責(zé)鏈中,一個(gè)請(qǐng)求在職責(zé)鏈中傳遞,只要有一個(gè)對(duì)象處理了這個(gè)請(qǐng)求,就會(huì)停止

現(xiàn)在稍稍變一下,改成一個(gè)請(qǐng)求在職責(zé)鏈中傳遞,每個(gè)職責(zé)對(duì)象負(fù)責(zé)處理請(qǐng)求的某一方面的功能,處理完成后,不是停止,而是繼續(xù)向下傳遞請(qǐng)求,當(dāng)請(qǐng)求通過很多職責(zé)對(duì)象處理過后,功能也就處理完了,把這樣的職責(zé)鏈稱為功能鏈。

考慮這樣一個(gè)功能,在實(shí)際應(yīng)用開發(fā)中,在進(jìn)行業(yè)務(wù)處理之前,通常需要進(jìn)行權(quán)限檢查、通用數(shù)據(jù)校驗(yàn)、數(shù)據(jù)邏輯校驗(yàn)等處理,然后才開始真正的業(yè)務(wù)邏輯實(shí)現(xiàn)??梢园堰@些功能分散到一個(gè)功能鏈中,這樣做的目的是使程序結(jié)構(gòu)更加靈活,而且復(fù)用性會(huì)更好,比如通用的權(quán)限檢查就只需要做一份,然后就可以在多個(gè)功能鏈中使用了。

有些朋友看到這里,可能會(huì)想,這不是可以使用裝飾模式來實(shí)現(xiàn)嗎?沒錯(cuò),可以使用裝飾模式來實(shí)現(xiàn)這樣的功能,但是職責(zé)鏈會(huì)更靈活一些,因?yàn)檠b飾模式是在已有的功能上增加新的功能,多個(gè)裝飾器之間會(huì)有一定的聯(lián)系;而職責(zé)鏈模式的各個(gè)職責(zé)對(duì)象實(shí)現(xiàn)的功能,相互之間是沒有關(guān)聯(lián)的,是自己實(shí)現(xiàn)屬于自己處理的那一份功能。

可能有些朋友會(huì)想到這很類似于在Web應(yīng)用開發(fā)中的過濾器Filter,沒錯(cuò),過濾器鏈就類似于一個(gè)功能鏈,每個(gè)過濾器負(fù)責(zé)自己的處理,然后轉(zhuǎn)交給下一個(gè)過濾器,直到把所有的過濾器都走完,然后進(jìn)入到Servlet里面進(jìn)行處理。最常見的過濾器功能,比如權(quán)限檢查、字符集轉(zhuǎn)換等,基本上都是Web應(yīng)用的標(biāo)配。

接下來在示例中,實(shí)現(xiàn)這樣的功能:實(shí)現(xiàn)商品銷售的業(yè)務(wù)處理,在真正進(jìn)行銷售的業(yè)務(wù)處理之前,需要對(duì)傳入處理的數(shù)據(jù),進(jìn)行權(quán)限檢查、通用數(shù)據(jù)檢查和數(shù)據(jù)邏輯檢查,只有這些檢查都能通過的情況下,才說明傳入的數(shù)據(jù)是正確的、有效的數(shù)據(jù),才可以進(jìn)行真正的業(yè)務(wù)功能處理。

  1. 首先定義已有的業(yè)務(wù)功能和封裝業(yè)務(wù)數(shù)據(jù)的對(duì)象,用前面出現(xiàn)過的那個(gè)保存銷售信息的業(yè)務(wù),為了簡單,就不去定義接口了,示例代碼如下:
/**
 * 商品銷售管理模塊的業(yè)務(wù)處理
 */
public class GoodsSaleEbo {
    /**
     * 保存銷售信息,本來銷售數(shù)據(jù)應(yīng)該是多條,太麻煩了,為了演示,簡單點(diǎn)
     * @param user 操作人員
     * @param customer 客戶
     * @param saleModel 銷售數(shù)據(jù)
     * @return 是否保存成功
     */
    public boolean sale(String user,String customer,SaleModel saleModel){
        //如果全部在這里處理,基本的順序是
        //1:權(quán)限檢查
        //2:通用數(shù)據(jù)檢查(這個(gè)也可能在表現(xiàn)層已經(jīng)作過了)
        //3:數(shù)據(jù)邏輯校驗(yàn)
     
        //4:真正的業(yè)務(wù)處理
     
        //但是現(xiàn)在通過功能鏈來做,這里就主要負(fù)責(zé)構(gòu)建鏈
        //暫時(shí)還沒有功能鏈,等實(shí)現(xiàn)好了各個(gè)處理對(duì)象再回來添加
        return true;
    }
}

對(duì)應(yīng)的封裝銷售數(shù)據(jù)的對(duì)象,示例代碼如下:

/**
 * 封裝銷售單的數(shù)據(jù),簡單的示意一下
 */
public class SaleModel {
    /**
     * 銷售的商品
     */
    private String goods;
    /**
     * 銷售的數(shù)量
     */
    private int saleNum;
    public String getGoods() {
        return goods;
    }
    public void setGoods(String goods) {
        this.goods = goods;
    }
    public int getSaleNum() {
        return saleNum;
    }
    public void setSaleNum(int saleNum) {
        this.saleNum = saleNum;
    }
    public String toString(){
        return "商品名稱="+goods+",銷售數(shù)量="+saleNum;
    }
}
  1. 定義一個(gè)用來處理保存銷售數(shù)據(jù)功能的職責(zé)對(duì)象的接口,示例代碼如下:
/**
 * 定義職責(zé)對(duì)象的接口
 */
public abstract class SaleHandler {
    /**
     * 持有下一個(gè)處理請(qǐng)求的對(duì)象
     */
    protected SaleHandler successor = null;
    /**
     * 設(shè)置下一個(gè)處理請(qǐng)求的對(duì)象
     * @param successor 下一個(gè)處理請(qǐng)求的對(duì)象
     */
    public void setSuccessor(SaleHandler successor){
        this.successor = successor;
    }
    /**
     * 處理保存銷售信息的請(qǐng)求
     * @param user 操作人員
     * @param customer 客戶
     * @param saleModel 銷售數(shù)據(jù)
     * @return 是否處理成功
     */
    public abstract boolean sale(String user,String customer,SaleModel saleModel);
}
  1. 實(shí)現(xiàn)各個(gè)職責(zé)處理對(duì)象,每個(gè)職責(zé)對(duì)象負(fù)責(zé)請(qǐng)求的一個(gè)方面的處理,把這些職責(zé)對(duì)象都走完了,功能也就實(shí)現(xiàn)完了。先定義處理安全檢查的職責(zé)對(duì)象,示例代碼如下:
/**
 * 進(jìn)行權(quán)限檢查的職責(zé)對(duì)象
 */
public class SaleSecurityCheck extends SaleHandler{
    public boolean sale(String user, String customer, SaleModel saleModel) {
        //進(jìn)行權(quán)限檢查,簡單點(diǎn),就小李能通過
        if("小李".equals(user)){
            return this.successor.sale(user, customer, saleModel);
        }else{
            System.out.println("對(duì)不起"+user+",你沒有保存銷售信息的權(quán)限");
            return false;
        }     
    }
}

接下來定義通用數(shù)據(jù)檢查的職責(zé)對(duì)象,示例代碼如下:

/**
 * 進(jìn)行數(shù)據(jù)通用檢查的職責(zé)對(duì)象
 */
public class SaleDataCheck extends SaleHandler{
    public boolean sale(String user, String customer, SaleModel saleModel) {
        //進(jìn)行數(shù)據(jù)通用檢查,稍麻煩點(diǎn),每個(gè)數(shù)據(jù)都要檢測
        if(user==null || user.trim().length()==0){
            System.out.println("申請(qǐng)人不能為空");
            return false;
        }
        if(customer==null || customer.trim().length()==0){
            System.out.println("客戶不能為空");
            return false;
        }
        if(saleModel==null ){
            System.out.println("銷售商品的數(shù)據(jù)不能為空");
            return false;
        }
        if(saleModel.getGoods() == null || saleModel.getGoods().trim().length()==0){
            System.out.println("銷售的商品不能為空");
            return false;
        }
        if(saleModel.getSaleNum()==0){
            System.out.println("銷售商品的數(shù)量不能為0");
            return false;
        }     
        //如果通過了上面的檢測,那就向下繼續(xù)執(zhí)行
        return this.successor.sale(user, customer, saleModel);
     }
}

再看看進(jìn)行數(shù)據(jù)邏輯檢查的職責(zé)對(duì)象,示例代碼如下:

/**
 * 進(jìn)行數(shù)據(jù)邏輯檢查的職責(zé)對(duì)象
 */
public class SaleLogicCheck extends SaleHandler{
    public boolean sale(String user, String customer, SaleModel saleModel) {
        //進(jìn)行數(shù)據(jù)的邏輯檢查,比如檢查ID的唯一性,主外鍵的對(duì)應(yīng)關(guān)系等等
        //這里應(yīng)該檢查這種主外鍵的對(duì)應(yīng)關(guān)系,比如銷售商品是否存在
        //為了演示簡單,直接通過吧
     
        //如果通過了上面的檢測,那就向下繼續(xù)執(zhí)行
        return this.successor.sale(user, customer, saleModel);
    }
}

最后是真正的業(yè)務(wù)處理的職責(zé)對(duì)象,示例代碼如下:

/**
 * 真正處理銷售的業(yè)務(wù)功能的職責(zé)對(duì)象
 */
public class SaleMgr extends SaleHandler{
    public boolean sale(String user, String customer, SaleModel saleModel) {
        //進(jìn)行真正的業(yè)務(wù)邏輯處理
        System.out.println(user+"保存了"+customer+"購買 "+saleModel+" 的銷售數(shù)據(jù)");
        return true;
    }
}
  1. 實(shí)現(xiàn)好了各個(gè)職責(zé)對(duì)象處理,回過頭來看看如何具體實(shí)現(xiàn)業(yè)務(wù)處理,在業(yè)務(wù)對(duì)象里面進(jìn)行功能鏈的組合,示例代碼如下:
public class GoodsSaleEbo {
    /**
     * 保存銷售信息,本來銷售數(shù)據(jù)應(yīng)該是多條,太麻煩了,為了演示,簡單點(diǎn)
     * @param user 操作人員
     * @param customer 客戶
     * @param saleModel 銷售數(shù)據(jù)
     * @return 是否保存成功
     */
    public boolean sale(String user,String customer,SaleModel saleModel){
        //如果全部在這里處理,基本的順序是
        //1:權(quán)限檢查
        //2:通用數(shù)據(jù)檢查(這個(gè)也可能在表現(xiàn)層已經(jīng)作過了)
        //3:數(shù)據(jù)邏輯校驗(yàn)
     
        //4:真正的業(yè)務(wù)處理
     
        //但是現(xiàn)在通過功能鏈來做,這里就主要負(fù)責(zé)構(gòu)建鏈
        SaleSecurityCheck ssc = new SaleSecurityCheck();
        SaleDataCheck sdc = new SaleDataCheck();
        SaleLogicCheck slc = new SaleLogicCheck();
        SaleMgr sd = new SaleMgr();
        ssc.setSuccessor(sdc);
        sdc.setSuccessor(slc);
        slc.setSuccessor(sd);
        //向鏈上的第一個(gè)對(duì)象發(fā)出處理的請(qǐng)求
        return ssc.sale(user, customer, saleModel);
    }
}
  1. 寫個(gè)客戶端,調(diào)用業(yè)務(wù)對(duì)象,測試一下看看,示例代碼如下:
public class Client {
    public static void main(String[] args) {
        //創(chuàng)建業(yè)務(wù)對(duì)象
        GoodsSaleEbo ebo = new GoodsSaleEbo();
        //準(zhǔn)備測試數(shù)據(jù)
        SaleModel saleModel = new SaleModel();
        saleModel.setGoods("張學(xué)友懷舊經(jīng)典");
        saleModel.setSaleNum(10);
     
        //調(diào)用業(yè)務(wù)功能
        ebo.sale("小李", "張三", saleModel);
        ebo.sale("小張", "李四", saleModel);
    }
}

運(yùn)行一下,試試看,運(yùn)行結(jié)果如下:

小李保存了張三購買 商品名稱=張學(xué)友懷舊經(jīng)典,銷售數(shù)量=10 的銷售數(shù)據(jù)
對(duì)不起小張,你沒有保存銷售信息的權(quán)限

3.4 職責(zé)鏈模式的優(yōu)缺點(diǎn)##

  1. 請(qǐng)求者和接收者松散耦合

在職責(zé)鏈模式里面,請(qǐng)求者并不知道接收者是誰,也不知道具體如何處理,請(qǐng)求者只是負(fù)責(zé)向職責(zé)鏈發(fā)出請(qǐng)求就可以了。而每個(gè)職責(zé)對(duì)象也不用管請(qǐng)求者或者是其它的職責(zé)對(duì)象,只負(fù)責(zé)處理自己的部分,其它的就交由其它的職責(zé)對(duì)象去處理。也就是說,請(qǐng)求者和接收者是完全解耦的。

  1. 動(dòng)態(tài)組合職責(zé)

職責(zé)鏈模式會(huì)把功能處理分散到單獨(dú)的職責(zé)對(duì)象里面,然后在使用的時(shí)候,可以動(dòng)態(tài)組合職責(zé)形成職責(zé)鏈,從而可以靈活的給對(duì)象分配職責(zé),也可以靈活的實(shí)現(xiàn)和改變對(duì)象的職責(zé)。

  1. 產(chǎn)生很多細(xì)粒度對(duì)象

職責(zé)鏈模式會(huì)把功能處理分散到單獨(dú)的職責(zé)對(duì)象里面,也就是每個(gè)職責(zé)對(duì)象只是處理一個(gè)方面的功能,要把整個(gè)業(yè)務(wù)處理完,需要大量的職責(zé)對(duì)象的組合,這會(huì)產(chǎn)生大量的細(xì)粒度職責(zé)對(duì)象。

  1. 不一定能被處理

職責(zé)鏈模式的每個(gè)職責(zé)對(duì)象只負(fù)責(zé)自己處理的那一部分,因此可能會(huì)出現(xiàn)某個(gè)請(qǐng)求,把整個(gè)鏈傳遞完了,都沒有職責(zé)對(duì)象處理它。這就需要在使用職責(zé)鏈模式的時(shí)候注意,需要提供默認(rèn)的處理,并且注意構(gòu)建的鏈的有效性。

3.5 思考職責(zé)鏈模式##

  1. 職責(zé)鏈模式的本質(zhì)

職責(zé)鏈模式的本質(zhì):分離職責(zé),動(dòng)態(tài)組合。

分離職責(zé)是前提,只有先把復(fù)雜功能分開,拆分成很多的步驟和小的功能處理,然后才能合理規(guī)劃和定義職責(zé)類,可以有很多的職責(zé)類來負(fù)責(zé)處理某一個(gè)功能,讓每個(gè)職責(zé)類負(fù)責(zé)處理功能的某一個(gè)方面,在運(yùn)行期間進(jìn)行動(dòng)態(tài)組合,形成一個(gè)處理的鏈,把這個(gè)鏈運(yùn)行完,那么功能也就處理完了。

動(dòng)態(tài)組合才是職責(zé)鏈模式的精華所在,因?yàn)橐獙?shí)現(xiàn)請(qǐng)求對(duì)象和處理對(duì)象的解耦,請(qǐng)求對(duì)象不知道誰才是真正的處理對(duì)象,因此要?jiǎng)討B(tài)的把可能的處理對(duì)象組合起來,由于組合的方式是動(dòng)態(tài)的,這就意味著可以很方便的修改和添加新的處理對(duì)象,從而讓系統(tǒng)更加靈活和具有更好的擴(kuò)展性。

當(dāng)然這么做還會(huì)有一個(gè)潛在的優(yōu)點(diǎn),就是可以增強(qiáng)職責(zé)功能的復(fù)用性。如果職責(zé)功能是很多地方都可以使用的公共功能,那么它可以應(yīng)用在多個(gè)職責(zé)鏈中復(fù)用。

  1. 何時(shí)選用職責(zé)鏈模式

建議在如下情況中,選用職責(zé)鏈模式:

如果有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,但是具體由哪個(gè)對(duì)象來處理該請(qǐng)求,是運(yùn)行時(shí)刻動(dòng)態(tài)確定的。這種情況可以使用職責(zé)鏈模式,把處理請(qǐng)求的對(duì)象實(shí)現(xiàn)成為職責(zé)對(duì)象,然后把它們構(gòu)成一個(gè)職責(zé)鏈,當(dāng)請(qǐng)求在這個(gè)鏈中傳遞的時(shí)候,具體由哪個(gè)職責(zé)對(duì)象來處理,會(huì)在運(yùn)行時(shí)動(dòng)態(tài)判斷。

如果你想在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求的話,可以使用職責(zé)鏈模式,職責(zé)鏈模式實(shí)現(xiàn)了請(qǐng)求者和接收者之間的解耦,請(qǐng)求者不需要知道究竟是哪一個(gè)接收者對(duì)象來處理了請(qǐng)求。

如果想要?jiǎng)討B(tài)指定處理一個(gè)請(qǐng)求的對(duì)象集合,可以使用職責(zé)鏈模式,職責(zé)鏈模式能動(dòng)態(tài)的構(gòu)建職責(zé)鏈,也就是動(dòng)態(tài)的來決定到底哪些職責(zé)對(duì)象來參與到處理請(qǐng)求中來,相當(dāng)于是動(dòng)態(tài)指定了處理一個(gè)請(qǐng)求的職責(zé)對(duì)象集合。

3.6 相關(guān)模式##

  1. 職責(zé)鏈模式和組合模式

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

可以把職責(zé)對(duì)象通過組合模式來組合,這樣可以通過組合對(duì)象自動(dòng)遞歸的向上調(diào)用,由父組件作為子組件的后繼,從而形成鏈。

這也就是前面提到過的使用外部已有的鏈接,這種情況在客戶端使用的時(shí)候,就不用再構(gòu)造鏈了,雖然不構(gòu)造鏈,但是需要構(gòu)造組合對(duì)象樹,是一樣的。

  1. 職責(zé)鏈模式和裝飾模式

這兩個(gè)模式相似,從某個(gè)角度講,可以相互模擬實(shí)現(xiàn)對(duì)方的功能

裝飾模式能夠動(dòng)態(tài)的給被裝飾對(duì)象添加功能,要求裝飾器對(duì)象和被裝飾的對(duì)象實(shí)現(xiàn)相同的接口。而職責(zé)鏈模式可以實(shí)現(xiàn)動(dòng)態(tài)的職責(zé)組合,標(biāo)準(zhǔn)的功能是有一個(gè)對(duì)象處理就結(jié)束,但是如果處理完本職責(zé)不急于結(jié)束,而是繼續(xù)向下傳遞請(qǐng)求,那么功能就和裝飾模式的功能差不多了,每個(gè)職責(zé)對(duì)象就類似于裝飾器,可以實(shí)現(xiàn)某種功能。

而且兩個(gè)模式的本質(zhì)也類似,都需要在運(yùn)行期間動(dòng)態(tài)組合,裝飾模式是動(dòng)態(tài)組合裝飾器,而職責(zé)鏈?zhǔn)莿?dòng)態(tài)組合處理請(qǐng)求的職責(zé)對(duì)象的鏈。

但是從標(biāo)準(zhǔn)的設(shè)計(jì)模式上來講,這兩個(gè)模式還是有較大區(qū)別的,這點(diǎn)要注意。首先是目的不同,裝飾模式是要實(shí)現(xiàn)透明的為對(duì)象添加功能,而職責(zé)鏈模式是要實(shí)現(xiàn)發(fā)送者和接收者解耦;另外一個(gè),裝飾模式是無限遞歸調(diào)用的,可以有任意多個(gè)對(duì)象來裝飾功能,但是職責(zé)鏈模式是有一個(gè)處理就結(jié)束

  1. 職責(zé)鏈模式和策略模式

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

這兩個(gè)模式有相似之處,如果把職責(zé)鏈簡化到直接就能選擇到相應(yīng)的處理對(duì)象,那就跟策略模式的選擇差不多,因此可以用職責(zé)鏈來模擬策略模式的功能。只是如果把職責(zé)鏈簡化到這個(gè)地步,也就不存在鏈了,也就稱不上是職責(zé)鏈了。

兩個(gè)模式可以組合使用,可以在職責(zé)鏈模式的某個(gè)職責(zé)的實(shí)現(xiàn)的時(shí)候,使用策略模式來選擇具體的實(shí)現(xiàn),同樣也可以在策略模式的某個(gè)策略實(shí)現(xiàn)里面,使用職責(zé)鏈模式來實(shí)現(xiàn)功能處理。

同理職責(zé)鏈模式也可以和狀態(tài)模式組合使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容