責(zé)任鏈模式

責(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ā)送者和接收者之間的耦合。

責(zé)任鏈模式UML.png

四嘱腥、代碼示例

案例一:公司處理員工請(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)求)

過(guò)濾器鏈.png

④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)近弟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挺智,隨后出現(xiàn)的幾起案子祷愉,更是在濱河造成了極大的恐慌,老刑警劉巖赦颇,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件二鳄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡媒怯,警方通過(guò)查閱死者的電腦和手機(jī)订讼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扇苞,“玉大人躯嫉,你說(shuō)我怎么就攤上這事⊙罟眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵擂啥,是天一觀的道長(zhǎng)哄陶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)哺壶,這世上最難降的妖魔是什么屋吨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮山宾,結(jié)果婚禮上至扰,老公的妹妹穿的比我還像新娘。我一直安慰自己资锰,他們只是感情好敢课,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般直秆。 火紅的嫁衣襯著肌膚如雪濒募。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天圾结,我揣著相機(jī)與錄音瑰剃,去河邊找鬼。 笑死筝野,一個(gè)胖子當(dāng)著我的面吹牛晌姚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歇竟,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼挥唠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了途蒋?” 一聲冷哼從身側(cè)響起猛遍,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎号坡,沒(méi)想到半個(gè)月后懊烤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宽堆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年腌紧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畜隶。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壁肋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出籽慢,到底是詐尸還是另有隱情浸遗,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布箱亿,位于F島的核電站跛锌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏届惋。R本人自食惡果不足惜髓帽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脑豹。 院中可真熱鬧郑藏,春花似錦、人聲如沸瘩欺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筑悴,卻和暖如春们拙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阁吝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工砚婆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人突勇。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓装盯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親甲馋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子埂奈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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