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é)鏈模式呢?
- 職責(zé)鏈模式定義

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

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é)鏈模式示例代碼##
- 先來看看職責(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();
}
- 接下來看看具體的職責(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的示意代碼幾乎是一樣的,因此就不去贅述了。
- 接下來看看客戶端的示意,示例代碼如下:
/**
* 職責(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é)的抽象類
首先來看看定義所有職責(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);
}
- 實(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;
}
}
- 使用職責(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)方式不同而已。
- 如何運(yùn)行
理解了示例的整體結(jié)構(gòu)和具體實(shí)現(xiàn),那么示例的具體運(yùn)行過程是怎樣的呢?
下面就以“小李申請(qǐng)聚餐費(fèi)用1200元”這個(gè)費(fèi)用申請(qǐng)為例來說明,調(diào)用過程的示意圖如圖所示:

3 模式講解#
3.1 認(rèn)識(shí)職責(zé)鏈模式##
- 模式功能
職責(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)求的功能鏈。
- 隱式接收者
當(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ì)象來處理的,因此稱為隱式接收者。
- 如何構(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)理這樣的鏈。
- 誰來處理
職責(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ì)象。
- 請(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)呢?
- 簡單的處理方式
要解決這個(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)求
- 通用請(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ù)功能處理。
- 首先定義已有的業(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;
}
}
- 定義一個(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);
}
- 實(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;
}
}
- 實(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);
}
}
- 寫個(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)##
- 請(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)求者和接收者是完全解耦的。
- 動(dòng)態(tài)組合職責(zé)
職責(zé)鏈模式會(huì)把功能處理分散到單獨(dú)的職責(zé)對(duì)象里面,然后在使用的時(shí)候,可以動(dòng)態(tài)組合職責(zé)形成職責(zé)鏈,從而可以靈活的給對(duì)象分配職責(zé),也可以靈活的實(shí)現(xiàn)和改變對(duì)象的職責(zé)。
- 產(chǎn)生很多細(xì)粒度對(duì)象
職責(zé)鏈模式會(huì)把功能處理分散到單獨(dú)的職責(zé)對(duì)象里面,也就是每個(gè)職責(zé)對(duì)象只是處理一個(gè)方面的功能,要把整個(gè)業(yè)務(wù)處理完,需要大量的職責(zé)對(duì)象的組合,這會(huì)產(chǎn)生大量的細(xì)粒度職責(zé)對(duì)象。
- 不一定能被處理
職責(zé)鏈模式的每個(gè)職責(zé)對(duì)象只負(fù)責(zé)自己處理的那一部分,因此可能會(huì)出現(xiàn)某個(gè)請(qǐng)求,把整個(gè)鏈傳遞完了,都沒有職責(zé)對(duì)象處理它。這就需要在使用職責(zé)鏈模式的時(shí)候注意,需要提供默認(rèn)的處理,并且注意構(gòu)建的鏈的有效性。
3.5 思考職責(zé)鏈模式##
- 職責(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ù)用。
- 何時(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)模式##
- 職責(zé)鏈模式和組合模式
這兩個(gè)模式可以組合使用。
可以把職責(zé)對(duì)象通過組合模式來組合,這樣可以通過組合對(duì)象自動(dòng)遞歸的向上調(diào)用,由父組件作為子組件的后繼,從而形成鏈。
這也就是前面提到過的使用外部已有的鏈接,這種情況在客戶端使用的時(shí)候,就不用再構(gòu)造鏈了,雖然不構(gòu)造鏈,但是需要構(gòu)造組合對(duì)象樹,是一樣的。
- 職責(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é)束。
- 職責(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)模式組合使用。