責(zé)任鏈模式
一入热、概念
是一個(gè)請(qǐng)求有多個(gè)對(duì)象來(lái)處理,這些對(duì)象是一條鏈晓铆,但具體由哪個(gè)對(duì)象來(lái)處理勺良,根據(jù)條件判斷來(lái)確定,如果不能處理會(huì)傳遞給該鏈中的下一個(gè)對(duì)象骄噪,直到有對(duì)象處理它為止尚困。
二、使用場(chǎng)景
- 有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求链蕊,具體哪個(gè)對(duì)象處理該請(qǐng)求待運(yùn)行時(shí)刻再確定事甜。
- 在不明確指定接受者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求滔韵。
- 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求逻谦,客戶端可以動(dòng)態(tài)創(chuàng)建職責(zé)鏈來(lái)處理請(qǐng)求。
三陪蜻、UML結(jié)構(gòu)圖
將接收者對(duì)象連成一條鏈邦马,并在該鏈上傳遞請(qǐng)求,直到有一個(gè)接收者對(duì)象處理它宴卖。通過(guò)讓更多對(duì)象有機(jī)會(huì)處理請(qǐng)求滋将,避免了請(qǐng)求發(fā)送者和接收者之間的耦合。
四嘱腥、代碼示例
案例一:公司處理員工請(qǐng)假請(qǐng)求
Handler:
public abstract class Handler {
private Handler nextHandler;
//當(dāng)前領(lǐng)導(dǎo)能審批通過(guò)的最多天數(shù)
public int maxDay;
protected Handler(int maxDay){
this.maxDay = maxDay;
}
//設(shè)置責(zé)任鏈中下一個(gè)處理請(qǐng)求的對(duì)象
public void setNextHandler(Handler handler){
nextHandler = handler;
}
protected void handleRequest(int day){
if(day <= maxDay){
reply(day);
}else{
if(nextHandler != null){
//審批權(quán)限不夠耕渴,繼續(xù)上班
nextHandler.handleRequest(day);
}else{
System.out.println("沒(méi)有更高的領(lǐng)導(dǎo)審批了");
}
}
}
//交由具體的handler來(lái)實(shí)現(xiàn)
protected abstract void reply(int day);
}
ProjectManager:
public class ProjectManager extends Handler{
public ProjectManager(int maxDay) {
super(maxDay);
}
@Override
protected void reply(int day) {
System.out.println(day+"天請(qǐng)假,項(xiàng)目經(jīng)理直接審批通過(guò)");
}
}
案例二:銷(xiāo)售團(tuán)隊(duì)處理客戶需求
銷(xiāo)售團(tuán)隊(duì)的層級(jí)關(guān)系:
Sales:<=5%
Manager:<=30%
Director:<=40%
Vice President:<=50%
CEO:<=55%
PriceHandler:
/**
* 價(jià)格處理人:負(fù)責(zé)處理客戶的折扣申請(qǐng)
* 使用抽象類(lèi)作為Handler的載體齿兔,
* 因?yàn)镠andler需要有一個(gè)指向自身類(lèi)型的引用橱脸,使用interface不方便
* @author HCX
*
*/
public abstract class PriceHandler {
/**
* 直接后繼,用于傳遞請(qǐng)求
* 指向自身類(lèi)型的引用
* protected:使子類(lèi)都可以訪問(wèn)到
*/
protected PriceHandler successor;
public void setSuccessor(PriceHandler successor) {
this.successor = successor;
}
/**
* 處理折扣請(qǐng)求
* @param discount
*/
public abstract void processDiscount(float discount);
/**
* 創(chuàng)建PriceHandler的工廠方法
* @return
*/
public static PriceHandler createPriceHandler() {
PriceHandler sales = new Sales();
PriceHandler manager = new Manager();
PriceHandler director = new Director();
PriceHandler vicePresident = new VicePresident();
PriceHandler ceo = new CEO();
//設(shè)置直接后繼
sales.setSuccessor(manager);
manager.setSuccessor(director);
director.setSuccessor(vicePresident);
vicePresident.setSuccessor(ceo);
return sales;
}
}
Sales:
/**
* 銷(xiāo)售人員分苇,可以批準(zhǔn)5%以內(nèi)的折扣
* @author HCX
*
*/
public class Sales extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount <= 0.05){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(),discount);
}else{//讓直接后繼來(lái)處理
successor.processDiscount(discount);
}
}
}
Manager:
/**
* 銷(xiāo)售經(jīng)理添诉,可以批準(zhǔn)30%以內(nèi)的折扣
* @author HCX
*
*/
public class Manager extends PriceHandler{
@Override
public void processDiscount(float discount) {
if(discount <= 0.3){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(),discount);
}else{//超過(guò)直接傳遞給直接后繼
successor.processDiscount(discount);
}
}
}
Director:
/**
* 銷(xiāo)售總監(jiān),可以批準(zhǔn)40%以內(nèi)的折扣
* @author HCX
*
*/
public class Director extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount <= 0.4){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(),discount);
}else{//超過(guò)直接傳遞給直接后繼
successor.processDiscount(discount);
}
}
}
VicePresident:
/**
* 銷(xiāo)售副總裁医寿,可以批準(zhǔn)50%以內(nèi)的折扣
* @author HCX
*
*/
public class VicePresident extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount <= 0.5){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(),discount);
}else{//超過(guò)直接傳遞給直接后繼
successor.processDiscount(discount);
}
}
}
CEO:
/**
* CEO栏赴,可以批準(zhǔn)55%以內(nèi)的折扣
* 折扣超出55%,拒絕申請(qǐng)
* @author HCX
*
*/
public class CEO extends PriceHandler{
@Override
public void processDiscount(float discount) {
if(discount <= 0.55){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(),discount);
}else{//讓直接后繼來(lái)處理
System.out.format("%s拒絕了折扣:%.2f%n", this.getClass().getName(),discount);
}
}
}
Customer:
/**
* 客戶靖秩,請(qǐng)求折扣
* @author HCX
*
*/
public class Customer {
private PriceHandler priceHandler;
public void setPriceHandler(PriceHandler priceHandler) {
this.priceHandler = priceHandler;
}
//只關(guān)心折扣請(qǐng)求是否被處理了须眷,不關(guān)心被誰(shuí)處理的竖瘾。
public void requestDiscount(float discount){
priceHandler.processDiscount(discount);
}
public static void main(String[] args) {
Customer customer = new Customer();
customer.setPriceHandler(PriceHandler.createPriceHandler());
Random random = new Random();
for(int i=1;i<100;i++){
System.out.println(i+":");
customer.requestDiscount(random.nextFloat());
}
}
}
修改:在Sales和manager之間加入Lead層級(jí):
加入了新的類(lèi)Lead,并對(duì)工廠方法進(jìn)行了改動(dòng)
Lead:
/**
* 銷(xiāo)售小組長(zhǎng)花颗,可以批準(zhǔn)15%以內(nèi)的折扣
* @author HCX
*
*/
public class Lead extends PriceHandler{
@Override
public void processDiscount(float discount) {
if(discount <= 0.15){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(),discount);
}else{//超過(guò)直接傳遞給直接后繼
successor.processDiscount(discount);
}
}
}
PriceHandler:
/**
* 價(jià)格處理人:負(fù)責(zé)處理客戶的折扣申請(qǐng)
* 使用抽象類(lèi)作為Handler的載體捕传,
* 因?yàn)镠andler需要有一個(gè)指向自身類(lèi)型的引用,使用interface不方便
* @author HCX
*
*/
public abstract class PriceHandler {
/**
* 直接后繼扩劝,用于傳遞請(qǐng)求
* 指向自身類(lèi)型的引用
* protected:使子類(lèi)都可以訪問(wèn)到
*/
protected PriceHandler successor;
public void setSuccessor(PriceHandler successor) {
this.successor = successor;
}
/**
* 處理折扣請(qǐng)求
* @param discount
*/
public abstract void processDiscount(float discount);
/**
* 創(chuàng)建PriceHandler的工廠方法
* @return
*/
public static PriceHandler createPriceHandler() {
PriceHandler sales = new Sales();
PriceHandler lead = new Lead();
PriceHandler manager = new Manager();
PriceHandler director = new Director();
PriceHandler vicePresident = new VicePresident();
PriceHandler ceo = new CEO();
//設(shè)置直接后繼
sales.setSuccessor(lead);
lead.setSuccessor(manager);
manager.setSuccessor(director);
director.setSuccessor(vicePresident);
vicePresident.setSuccessor(ceo);
return sales;
}
}
改進(jìn):OO之中的原則:?jiǎn)我宦氊?zé)原則庸论,在設(shè)計(jì)一個(gè)接口時(shí),應(yīng)該只將與該接口業(yè)務(wù)相關(guān)的方法放在接口之中棒呛,這樣才能使設(shè)計(jì)更加健壯而不至于當(dāng)變化發(fā)生時(shí)聂示,需要修改多處。
PriceHandler:提供了處理業(yè)務(wù)的方法簇秒,也提供了創(chuàng)建PriceHandler實(shí)例的工廠方法鱼喉。這兩種方法在功能上不能類(lèi)聚的,兩者之間沒(méi)有關(guān)系趋观,該設(shè)計(jì)不符合單一職責(zé)原則蒲凶。
命名規(guī)范不符合見(jiàn)名知意的原則。
把工廠方法抽取出來(lái):
PriceHandlerFactory:
public class PriceHandlerFactory {
/**
* 創(chuàng)建PriceHandler的工廠方法
* @return
*/
public static PriceHandler createPriceHandler() {
PriceHandler sales = new Sales();
PriceHandler lead = new Lead();
PriceHandler manager = new Manager();
PriceHandler director = new Director();
PriceHandler vicePresident = new VicePresident();
PriceHandler ceo = new CEO();
//設(shè)置直接后繼
sales.setSuccessor(lead);
lead.setSuccessor(manager);
manager.setSuccessor(director);
director.setSuccessor(vicePresident);
vicePresident.setSuccessor(ceo);
return sales;
}
}
Customer:
/**
* 客戶拆内,請(qǐng)求折扣
* @author HCX
*
*/
public class Customer {
private PriceHandler priceHandler;
public void setPriceHandler(PriceHandler priceHandler) {
this.priceHandler = priceHandler;
}
//只關(guān)心折扣請(qǐng)求是否被處理了,不關(guān)心被誰(shuí)處理的宠默。
public void requestDiscount(float discount){
priceHandler.processDiscount(discount);
}
public static void main(String[] args) {
Customer customer = new Customer();
customer.setPriceHandler(PriceHandlerFactory.createPriceHandler());
Random random = new Random();
for(int i=1;i<100;i++){
System.out.println(i+":");
customer.requestDiscount(random.nextFloat());
}
}
}
PriceHandler:
/**
* 價(jià)格處理人:負(fù)責(zé)處理客戶的折扣申請(qǐng)
* 使用抽象類(lèi)作為Handler的載體麸恍,
* 因?yàn)镠andler需要有一個(gè)指向自身類(lèi)型的引用,使用interface不方便
* @author HCX
*
*/
public abstract class PriceHandler {
/**
* 直接后繼搀矫,用于傳遞請(qǐng)求
* 指向自身類(lèi)型的引用
* protected:使子類(lèi)都可以訪問(wèn)到
*/
protected PriceHandler successor;
public void setSuccessor(PriceHandler successor) {
this.successor = successor;
}
/**
* 處理折扣請(qǐng)求
* @param discount
*/
public abstract void processDiscount(float discount);
}
改進(jìn)之后抹沪,Customer類(lèi)只依賴于PriceHandler和PriceHandlerFactory兩個(gè)類(lèi),并沒(méi)有依賴實(shí)現(xiàn)的PriceHandler:Sales和Manager等瓤球;因此耦合度較低融欧。
五、在實(shí)際中的應(yīng)用
①try-catch語(yǔ)句:
每一個(gè)catch語(yǔ)句是根據(jù)Exception異常類(lèi)型進(jìn)行匹配的卦羡,一般會(huì)有多個(gè)catch語(yǔ)句噪馏,就形成了一個(gè)責(zé)任鏈;此時(shí)如果有一個(gè)catch語(yǔ)句與當(dāng)前所要處理的異常Exception符合時(shí)绿饵,該Exception就會(huì)交給相應(yīng)的catch語(yǔ)句進(jìn)行處理欠肾,之后的catch語(yǔ)句就不會(huì)再執(zhí)行了。
②異常處理機(jī)制:
方法的調(diào)用構(gòu)成了一個(gè)棧拟赊,當(dāng)棧頂?shù)姆椒óa(chǎn)生異常時(shí)刺桃,需要將異常拋出,被拋出的異常沿著調(diào)用棧向下發(fā)展吸祟,尋找一個(gè)處理的塊瑟慈,被棧頂拋出的方法作為一個(gè)請(qǐng)求桃移,而調(diào)用棧上的每一個(gè)方法就相當(dāng)于一個(gè)handler,該Handler即可以選擇自行處理這個(gè)被拋出的異常葛碧,也可以選擇將異常沿著調(diào)用棧傳遞下去借杰。
異常:請(qǐng)求
調(diào)用棧中的每一級(jí):Handler
調(diào)用棧中的handler:責(zé)任鏈
棧底元素:上一級(jí)元素的直接后繼
③過(guò)濾器鏈(一般鏈條中只有一個(gè)對(duì)象處理請(qǐng)求,但是過(guò)濾器鏈可以有多個(gè)對(duì)象同時(shí)處理請(qǐng)求)
④Spring Security框架
通過(guò)多個(gè)filter類(lèi)構(gòu)成一個(gè)鏈條來(lái)處理Http請(qǐng)求吹埠,從而為應(yīng)用提供一個(gè)認(rèn)證與授權(quán)的框架第步。
六、責(zé)任鏈模式內(nèi)部處理
在責(zé)任鏈模式中缘琅,作為請(qǐng)求接受者的多個(gè)對(duì)象通過(guò)對(duì)其后繼的引用而連接起來(lái)形成一條鏈粘都。請(qǐng)求在這條鏈上傳遞,直到鏈上某一個(gè)接收者處理這個(gè)請(qǐng)求刷袍。每個(gè)接收者都可以選擇自行處理請(qǐng)求或是向后繼傳遞請(qǐng)求
Handler設(shè)了一個(gè)自身類(lèi)型的對(duì)象作為其后繼翩隧,Handler是抽象的,從而整條鏈也是抽象的呻纹,這種抽象的特性使得在運(yùn)行時(shí)可以動(dòng)態(tài)的綁定鏈條中的對(duì)象堆生,從而提供了足夠的空間。
在代碼中直接后繼successor的類(lèi)型是PriceHandler,而不是任何其他具體的類(lèi)(Sales雷酪、Manager等)淑仆,使得在后面變更需求時(shí),加入了lead層次哥力,可以簡(jiǎn)單的實(shí)現(xiàn)蔗怠。所以責(zé)任鏈模式遵循了OO中的依賴倒置原則,即依賴于抽象而非依賴于具體吩跋。降低了程序的耦合度寞射。
發(fā)出請(qǐng)求的客戶端并不知道鏈上的哪一個(gè)接收者會(huì)處理這個(gè)請(qǐng)求,從而實(shí)現(xiàn)了客戶端和接收者之間的解耦锌钮。
七桥温、責(zé)任鏈模式的優(yōu)缺點(diǎn)
- 開(kāi)閉原則:對(duì)擴(kuò)展開(kāi)放,對(duì)變更關(guān)閉梁丘;
有業(yè)務(wù)變更時(shí)侵浸,希望通過(guò)新增一個(gè)類(lèi),而非修改原有的代碼來(lái)滿足業(yè)務(wù)需求氛谜。
在案例二中通惫,通過(guò)新增了一個(gè)lead類(lèi),但同時(shí)也修改了工廠方法createPriceHandler混蔼。
而實(shí)際的好壞還是取決于實(shí)際的項(xiàng)目需求履腋。因此對(duì)經(jīng)典原則的取舍需要根據(jù)實(shí)際項(xiàng)目情況決定。
- 執(zhí)行性能:在結(jié)構(gòu)上,責(zé)任鏈模式由處理器首尾相接構(gòu)成的一條鏈遵湖,當(dāng)由請(qǐng)求到來(lái)之時(shí)悔政,需要從鏈的頭部開(kāi)始遍歷整條責(zé)任鏈,直到有一個(gè)處理器處理了請(qǐng)求延旧,或者是整個(gè)鏈條遍歷完成谋国。在這個(gè)過(guò)程中性能的損耗體現(xiàn)在兩個(gè)方面:
時(shí)間:相對(duì)于單個(gè)handler處理請(qǐng)求的時(shí)間而言,整個(gè)鏈條遍歷的過(guò)程可能會(huì)消耗更多的時(shí)間迁沫。
內(nèi)存:創(chuàng)建了大量的對(duì)象來(lái)表示處理器對(duì)象芦瘾,但是實(shí)際僅僅使用了其中的少部分,剩余的大部分處理器都未被使用到集畅。
說(shuō)明:本文部分內(nèi)容來(lái)自慕課網(wǎng)近弟。