三國(guó)演義:責(zé)任鏈模式
大家好,我是老田,今天我給大家分享設(shè)計(jì)模式中的責(zé)任鏈模式。用貼切的生活故事,以及真實(shí)項(xiàng)目場(chǎng)景來(lái)講設(shè)計(jì)模式,最后用一句話來(lái)總結(jié)這個(gè)設(shè)計(jì)模式。
關(guān)于設(shè)計(jì)模式系列,前面我們已經(jīng)分享過(guò):
故事
前兩天,沒(méi)事又刷了一遍三國(guó)演義,看到關(guān)羽身在曹營(yíng)心在漢,聽(tīng)說(shuō)劉備在袁紹那里,然后就上演了“過(guò)五關(guān),斬六將”。
關(guān)羽過(guò)五關(guān)斬六將主要內(nèi)容:
- 第一關(guān),東嶺關(guān),斬守將孔秀。
 
東嶺關(guān),守關(guān)將名叫孔秀,本是黃巾余黨,歸降曹操之后,帶著五百人奉命防守東嶺關(guān)。關(guān)羽車隊(duì)從關(guān)前通過(guò)時(shí),孔秀索要通關(guān)文牒,與關(guān)羽發(fā)生沖突,只一個(gè)回合,就被關(guān)羽斬殺。
- 第二關(guān),洛陽(yáng)關(guān),孟坦和韓福。
 
關(guān)羽過(guò)了東嶺關(guān),在要過(guò)洛陽(yáng)時(shí),韓福、孟坦用鹿角攔住道路。先是孟坦挑戰(zhàn),與關(guān)羽說(shuō)翻,交手不敵,孟坦撥馬回跑,引關(guān)公來(lái)追,這樣韓福就可以在后面射箭擒拿關(guān)公,可誰(shuí)想到關(guān)公赤兔馬快,從后面趕上孟坦,一刀就把孟坦給劈了。韓?;诺蒙淞艘患?,中關(guān)公左臂,關(guān)公忍住箭傷,也沖過(guò)鹿角,一刀斬殺韓福,于是過(guò)洛陽(yáng)。
- 第三關(guān),汜水關(guān),卞喜。
 
在得知關(guān)羽過(guò)關(guān)斬將,東嶺關(guān)孔秀、洛陽(yáng)韓福、孟坦都被殺害,卞喜自思難以抵擋關(guān)公。于是就假意迎接關(guān)公,在鎮(zhèn)國(guó)寺安排下刀斧手,準(zhǔn)備伺機(jī)殺死關(guān)公。幸虧有鎮(zhèn)國(guó)寺老方丈普凈給警示,關(guān)公這才察覺(jué)出陰謀,與卞喜鬧翻,一刀斬殺卞喜,于是關(guān)公過(guò)汜水關(guān)。
- 第四關(guān),王植。
 
這王植是韓福的親家,聽(tīng)說(shuō)韓福被關(guān)公殺死,十分憤怒,于是就要為韓福報(bào)仇。在關(guān)公到達(dá)滎陽(yáng)時(shí),王植在館驛設(shè)宴,宴請(qǐng)關(guān)公和二位皇嫂。卻是暗中派從事胡班放火,想要燒死關(guān)公。但胡班因關(guān)公給父親胡華帶信的緣故,向關(guān)羽告了密。關(guān)羽和二位皇嫂得以提前逃離館驛,胡班卻假意放火,迷惑王植。不過(guò)王植后來(lái)察覺(jué),殺了胡班,來(lái)追關(guān)羽時(shí),被關(guān)羽斬殺,于是關(guān)公過(guò)滎陽(yáng)。
- 第五關(guān),黃河渡口,秦琪。
 
這秦琪不僅是夏侯惇的愛(ài)將,更是老將軍蔡陽(yáng)的外甥,奉命守衛(wèi)黃河渡口,盤查過(guò)往船只。關(guān)公到黃河渡口時(shí),要找船只渡河,被秦琪攔住,秦琪不僅不放關(guān)公等人渡河,反而口出狂言,終于激怒關(guān)公,被關(guān)公斬殺
這就是關(guān)羽過(guò)五關(guān)斬六將的全部過(guò)程。
這個(gè)故事情節(jié)讓我想起了一個(gè)設(shè)計(jì)模式:責(zé)任鏈模式。
其實(shí),我們生活中也有著非常多的責(zé)任鏈模式。比如:基本上每個(gè)公司都有自己的OA系統(tǒng),主要是員工基本信息、請(qǐng)假、調(diào)休、報(bào)銷等功能。如果,我有事需要請(qǐng)假兩天,于是登錄OA系統(tǒng),發(fā)起請(qǐng)假審批。
由于,對(duì)于請(qǐng)假時(shí)間的長(zhǎng)短公司有如下規(guī)定:
小于等于半天,審批環(huán)節(jié):項(xiàng)目負(fù)責(zé)人
大于半天,小于等于1天的,審批環(huán)節(jié):項(xiàng)目負(fù)責(zé)人+技術(shù)總監(jiān)
超過(guò)1天,審批環(huán)節(jié):項(xiàng)目負(fù)責(zé)人+技術(shù)總監(jiān)+Boss
可以看得出來(lái),我請(qǐng)假審批流程為項(xiàng)目負(fù)責(zé)人+技術(shù)總監(jiān)+Boss。
到底什么是責(zé)任鏈設(shè)計(jì)模式?
什么是責(zé)任鏈模式呢
責(zé)任鏈模式英文解釋為:
Avoid coupling the sender of a request to its receiver bygiving more than one object a chance to handle the request.Chainthe receiving objects and pass the request along the chain until anobject handles it.
責(zé)任鏈模式(Chain of Responsibility Pattern)將鏈中每一個(gè)節(jié)點(diǎn)都看作一個(gè)對(duì)象,每個(gè)節(jié)點(diǎn)處理的請(qǐng)求均不同,且內(nèi)部自動(dòng)維護(hù)下一個(gè)節(jié)點(diǎn)對(duì)象。當(dāng)一個(gè)請(qǐng)求從鏈?zhǔn)降氖锥税l(fā)出時(shí),會(huì)沿著責(zé)任鏈預(yù)設(shè)的路徑依次傳遞到每一個(gè)節(jié)點(diǎn)對(duì)象,直至被鏈中的某個(gè)對(duì)象處理為止,屬于行為型設(shè)計(jì)模式。
責(zé)任鏈模式通用代碼
Java實(shí)現(xiàn)責(zé)任鏈設(shè)計(jì)模式如下:
- public abstract class Handler {
 - protected Handler nextHandler = null;
 - public abstract void handle();
 - public Handler getNextHandler() {
 - return nextHandler;
 - }
 - public void setNextHandler(Handler nextHandler) {
 - this.nextHandler = nextHandler;
 - }
 - }
 - public class HandlerA extends Handler{
 - @Override
 - public void handle() {
 - if(nextHandler == null){
 - System.out.println("HandlerA handle ...");
 - }else{
 - nextHandler.handle();
 - }
 - }
 - }
 - public class HandlerB extends Handler{
 - @Override
 - public void handle() {
 - if(nextHandler == null){
 - System.out.println("HandlerB handle ...");
 - }else{
 - nextHandler.handle();
 - }
 - }
 - }
 - public class HandlerC extends Handler{
 - @Override
 - public void handle() {
 - if(getNextHandler() == null){
 - System.out.println("HandlerC handle ...");
 - }else{
 - getNextHandler().handle();
 - }
 - }
 - }
 - //測(cè)試
 - public class Client{
 - public static void main(String[] args) {
 - Handler handlerA = new HandlerA();
 - Handler handlerB = new HandlerB();
 - handlerA.setNextHandler(handlerB);
 - handlerA.handle();
 - }
 - }
 
運(yùn)行結(jié)果:
- HandlerC handle ...
 
從上面代碼,我們可以畫出UML圖:
從UML圖中,我們又可以看出,責(zé)任鏈模式中有兩個(gè)非常重要的角色:
(1)、抽象處理者角色(Handler)
定義處理請(qǐng)求的接口。接口可以也可以給出一個(gè)方法以設(shè)定和返回對(duì)下個(gè)對(duì)象引用。這個(gè)角色通常由一個(gè)Java抽象類或者Java接口實(shí)現(xiàn)。
(2)、具體處理者角色(HandlerA、HandlerB、HandlerC)
具體處理者接到請(qǐng)求后,可以選擇將請(qǐng)求處理掉,或者將請(qǐng)求傳給下個(gè)對(duì)象。由于具體處理者持有對(duì)下家的引用。
責(zé)任鏈模式的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):請(qǐng)求和處理分開(kāi),兩者解耦,提供系統(tǒng)的靈活性。
 - 缺點(diǎn):性能能問(wèn),一個(gè)鏈非常長(zhǎng)的時(shí)候,非常耗時(shí)。因?yàn)槲覀儽苊饨⒑荛L(zhǎng)的鏈。
 
生活中的案例
在日常生活中,責(zé)任鏈模式是比較常見(jiàn)的。我們平時(shí)處理工作中的一些事務(wù),往往是各部門協(xié)同合作來(lái)完成某一個(gè)任務(wù)的。而每個(gè)部門都有各自的職責(zé),因此,很多時(shí)候事情完成一半,便會(huì)轉(zhuǎn)交到下一個(gè)部門,直到所有部門都審批通過(guò),事情才能完成。
責(zé)任鏈模式主要解耦了請(qǐng)求與處理,客戶只需將請(qǐng)求發(fā)送到鏈上即可,不需要關(guān)心請(qǐng)求的具體內(nèi)容和處理細(xì)節(jié),請(qǐng)求會(huì)自動(dòng)進(jìn)行傳遞,直至有節(jié)點(diǎn)對(duì)象進(jìn)行處理。
責(zé)任鏈模式主要適用于以下應(yīng)用場(chǎng)景:
- 多個(gè)對(duì)象可以處理同一請(qǐng)求,但具體由哪個(gè)對(duì)象處理則在運(yùn)行時(shí)動(dòng)態(tài)決定。
 - 在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交請(qǐng)求。
 - 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求。
 
請(qǐng)假流程的代碼實(shí)現(xiàn)
下面我們來(lái)對(duì),前面的案例:OA上請(qǐng)假流程做一個(gè)Java代碼的實(shí)現(xiàn)。
抽象處理者:領(lǐng)導(dǎo)類
- public abstract class Leader {
 - private Leader next;
 - public void setNext(Leader next) {
 - this.next = next;
 - }
 - public Leader getNext() {
 - return next;
 - }
 - //處理請(qǐng)求的方法
 - public abstract void handleRequest(double LeaveDays);
 - }
 
項(xiàng)目負(fù)責(zé)人
- public class ProjectLeader extends Leader {
 - @Override
 - public void handleRequest(double LeaveDays) {
 - if (LeaveDays <= 0.5) {
 - System.out.println("項(xiàng)目負(fù)責(zé)人批準(zhǔn)您請(qǐng)假" + LeaveDays + "天。");
 - } else {
 - if (getNext() != null) {
 - getNext().handleRequest(LeaveDays);
 - } else {
 - System.out.println("請(qǐng)假天數(shù)太多,沒(méi)有人批準(zhǔn)該假條!");
 - }
 - }
 - }
 - }
 
技術(shù)總監(jiān)
- public class TechnicalDirectorLeader extends Leader {
 - @Override
 - public void handleRequest(double LeaveDays) {
 - if (LeaveDays <= 1) {
 - System.out.println("技術(shù)總監(jiān)批準(zhǔn)您請(qǐng)假" + LeaveDays + "天。");
 - } else {
 - if (getNext() != null) {
 - getNext().handleRequest(LeaveDays);
 - } else {
 - System.out.println("請(qǐng)假天數(shù)太多,沒(méi)有人批準(zhǔn)該假條!");
 - }
 - }
 - }
 - }
 
Boss
- public class BossLeader extends Leader {
 - @Override
 - public void handleRequest(double LeaveDays) {
 - if (LeaveDays >= 2 && LeaveDays <= 30) {
 - System.out.println("Boss批準(zhǔn)您請(qǐng)假" + LeaveDays + "天。");
 - } else {
 - if (getNext() != null) {
 - getNext().handleRequest(LeaveDays);
 - } else {
 - System.out.println("請(qǐng)假天數(shù)太多,沒(méi)有人批準(zhǔn)該假條!");
 - }
 - }
 - }
 - }
 
發(fā)起審批
- public class LeaveApproval {
 - public static void main(String[] args) {
 - //組裝責(zé)任鏈
 - Leader projectLeader = new ProjectLeader();
 - Leader technicalDirectorLeader = new TechnicalDirectorLeader();
 - Leader bossLeader = new BossLeader();
 - projectLeader.setNext(technicalDirectorLeader);
 - technicalDirectorLeader.setNext(bossLeader);
 - //請(qǐng)假兩天,提交請(qǐng)假流程,開(kāi)啟審批環(huán)節(jié),
 - projectLeader.handleRequest(2);
 - }
 - }
 
審批結(jié)果
- Boss批準(zhǔn)您請(qǐng)假2.0天。
 
如果請(qǐng)假天數(shù)是31天,審批結(jié)果
- 請(qǐng)假天數(shù)太多,沒(méi)有人批準(zhǔn)該假條!
 
整個(gè)請(qǐng)假流程為:
把這張流程圖改成縱向:
就這么一環(huán)套一環(huán)的,使用上面兩個(gè)例子和兩張圖來(lái)理解責(zé)任鏈模式是不是就更輕松了?
自己吹牛逼,沒(méi)什么用,下面來(lái)看看大神們是怎么使用責(zé)任鏈模式的。
大佬們是如何使用的
在Spring、Mybatis等框架中,都用使用到責(zé)任鏈模式,下面先來(lái)看在Spring中是如何使用的。
在Spring MVC中的org.springframework.web.servlet.DispatcherServlet類中:
getHandler 方法的處理使用到了責(zé)任鏈模式,handlerMappings是之前 Spring 容器初始化好的,通過(guò)遍歷 handlerMappings查找與request匹配的 Handler, 這里返回 HandlerExecutionChain 對(duì)象。這個(gè) HandlerExecutionChain對(duì)象到后面執(zhí)行的時(shí)候再分析為什么返回的是這樣一個(gè)對(duì)象。
- @Nullable
 - protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 - if (this.handlerMappings != null) {
 - for (HandlerMapping mapping : this.handlerMappings) {
 - HandlerExecutionChain handler = mapping.getHandler(request);
 - if (handler != null) {
 - return handler;
 - }
 - }
 - }
 - return null;
 - }
 
以上便是責(zé)任鏈模式在Spring的具體使用,關(guān)于Mybatis中責(zé)任鏈模式的使用,請(qǐng)看這篇文章:
總結(jié)
本文通過(guò)關(guān)二爺?shù)倪^(guò)五關(guān)斬六將和OA系統(tǒng)中的請(qǐng)假審批流程,完美的解釋了責(zé)任鏈設(shè)計(jì)模式。
最后用一句話來(lái)總結(jié)責(zé)任鏈模式:
各人自掃門前雪,莫管他人瓦上霜。
本文轉(zhuǎn)載自微信公眾號(hào)「Java后端技術(shù)全棧」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java后端技術(shù)全棧公眾號(hào)。




















 
 
 



 
 
 
 