職責鏈模式

一吟秩、什么是職責鏈模式

從文字角度出發(fā),我們可以先將關(guān)注點放在“鏈”字上绽淘,很容易聯(lián)想到鏈式結(jié)構(gòu)涵防,舉個生活中常見的例子,擊鼓傳花游戲就是一個很典型的鏈式結(jié)構(gòu)沪铭,所有人形成一條鏈壮池,相互傳遞。而從另一個角度說杀怠,職責鏈就是所謂的多級結(jié)構(gòu)椰憋,比如去醫(yī)院開具病假條,普通醫(yī)生只能開一天的證明赔退,如果需要更多時常橙依,則需將開具職責轉(zhuǎn)交到上級去,上級醫(yī)師只能開三天證明硕旗,如需更多時常窗骑,則需將職責轉(zhuǎn)交到他的上級,以此類推漆枚,這就是一個職責鏈模式的典型應(yīng)用创译。再比如公司請假,根據(jù)請假時常的不同墙基,需要遞交到的級別也不同软族,這種層級遞進的關(guān)系就是一種多級結(jié)構(gòu)刷喜。

職責鏈模式(Chain Of Responsibility),使多個對象都有機會處理請求立砸,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系掖疮。將這個對象連成一條鏈,并沿著這條鏈傳遞該請求仰禽,直到有一個對象處理它為止氮墨。UML結(jié)構(gòu)圖如下:

image

其中,Handler是抽象處理者吐葵,定義了一個處理請求的接口;ConcreteHandler是具體處理者桥氏,處理它所負責的請求温峭,可訪問它的后繼者,如果可處理該請求就處理字支,否則就將該請求轉(zhuǎn)發(fā)給它的后繼者凤藏。

1. 抽象處理者

抽象處理者實現(xiàn)了三個職責:

  • 定義一個請求的處理方法handlerMessage(),是唯一對外開放的方法
  • 定義一個鏈的編排方式setNext()堕伪,用于設(shè)置下一個處理者
  • 定義了具體的請求者必須實現(xiàn)的兩個方法揖庄,即定義自己能夠處理的級別的getHandlerLevel()方法及具體的處理任務(wù)echo()方法
public abstract class Handler {

    private Handler nextHandler;    //下一個處理者

    public final Response handlerMessage(Request request) {
        Response response = null;

        if(this.getHandlerLevel().equals(request.getRequestLevel())) {    //判斷是否是自己的處理級別
            response = this.echo(request);
        } else {
            if(this.nextHandler != null) {    //下一處理者不為空
                response = this.nextHandler.handlerMessage(request);
            } else {
                //沒有適當?shù)奶幚碚撸瑯I(yè)務(wù)自行處理
            }
        }

        return response;
    }

    //設(shè)定下一個處理者
    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }

    //每個處理者的處理等級
    protected abstract Level getHandlerLevel();

    //每個處理者都必須實現(xiàn)的處理任務(wù)
    protected abstract Response echo(Request request);

}

2. 具體處理者

這里我們定義三個具體處理者欠雌,以便能組成一條鏈蹄梢,ConcreteHandlerB及ConcreteHandlerC就不再贅述了。

public class ConcreteHandlerA extends Handler {

    @Override
    protected Level getHandlerLevel() {
        //設(shè)置自己的處理級別
        return null;
    }

    @Override
    protected Response echo(Request request) {
        //完成處理邏輯
        return null;
    }

}

3. Level類

Level類負責定義請求和處理級別富俄,具體內(nèi)容需根據(jù)業(yè)務(wù)產(chǎn)生禁炒。

 public class Level {     
 //定義一個請求和處理等級
}

4. Request類

Request類負責封裝請求,具體內(nèi)容需根據(jù)業(yè)務(wù)產(chǎn)生霍比。

public class Request {

    //請求的等級
    public Level getRequestLevel() {
        return null;
    }

}

5. Response類

Response類負責封裝鏈中返回的結(jié)果幕袱,具體內(nèi)容需根據(jù)業(yè)務(wù)產(chǎn)生。

 public class Response { 
    //處理者返回的數(shù)據(jù)
 }

6. Client客戶端

我們在場景類或高層模塊中對類進行組裝悠瞬,并傳遞請求们豌,返回結(jié)果。如下對三個具體處理者進行組裝浅妆,按照1→2→3的順序望迎,并得出返回結(jié)果。

public class Client {

    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandlerA();
        Handler handler2 = new ConcreteHandlerB();
        Handler handler3 = new ConcreteHandlerC();

        //設(shè)置鏈中的階段順序 1->2->3
        handler1.setNext(handler2);
        handler2.setNext(handler3);

        //提交請求返回結(jié)果
        Response response = handler1.handlerMessage(new Request());
    }

}

當然這是個未完成的模板狂打,最終結(jié)果會因為 request.getRequestLevel() 為空而拋出異常擂煞,具體內(nèi)容需根據(jù)業(yè)務(wù)邏輯進行編寫。

二趴乡、職責鏈模式的應(yīng)用

1. 何時使用

  • 處理消息時

2. 方法

  • 攔截的類都實現(xiàn)同一接口

3. 優(yōu)點

  • 將請求和處理分開对省,實現(xiàn)解耦蝗拿,提高系統(tǒng)的靈活性
  • 簡化了對象,使對象不需要知道鏈的結(jié)構(gòu)

4. 缺點

  • 性能會收到影響蒿涎,特別是在鏈比較長的時候
  • 調(diào)試不方便哀托。采用了類似遞歸的方式,調(diào)試時邏輯可能比較復(fù)雜
  • 不能保證請求一定被接收

5. 使用場景

  • 有多個對象可以處理同一個請求
  • 在不明確指定接收者的情況下劳秋,向多個對象中的提交請求
  • 可動態(tài)指定一組對象處理請求

6. 應(yīng)用實例

  • 多級請求
  • 擊鼓傳花
  • 請假/加薪請求
  • Java Web中Tomcat對Encoding的處理仓手、攔截器

7. 注意事項

  • 需控制鏈中最大節(jié)點數(shù)量,一般通過在Handler中設(shè)置一個最大節(jié)點數(shù)量玻淑,在setNext()方法中判斷是否已經(jīng)超過閥值嗽冒,超過則不允許該鏈建立,避免出現(xiàn)超長鏈無意識地破壞系統(tǒng)性能

三补履、職責鏈模式的實現(xiàn)

我們就以請假/加薪為例添坊,實現(xiàn)一個較為簡單的職責鏈模式。UML圖如下:

image

1. 抽象管理者

通過Manager抽象類管理所有管理者箫锤,setSuperior()方法用于定義職責鏈的下一級贬蛙,即定義當前管理者的上級。

public abstract class Manager {

    protected String name;
    protected Manager superior;    //管理者的上級

    public Manager(String name) {
        this.name = name;
    }

    //設(shè)置管理者的上級
    public void setSuperior(Manager superior) {
        this.superior = superior;
    }

    //申請請求
    public abstract void handlerRequest(Request request);

}

2. 具體管理者

經(jīng)理類如下谚攒,只可批準兩天以內(nèi)的假期阳准,其余請求將繼續(xù)申請上級。

public class CommonManager extends Manager {

    public CommonManager(String name) {
        super(name);
    }

    @Override
    public void handlerRequest(Request request) {
        if (request.getRequestType().equals("請假") && request.getNumber() <= 2) {    //只能批準兩天內(nèi)的假期
            System.out.println(name + ":" + request.getRequestContent() + "馏臭,時長:" + request.getNumber() + "天野蝇,被批準");
        } else {    //其余請求申請上級
            if (superior != null) {
                superior.handlerRequest(request);
            }
        }
    }

}

總監(jiān)類如下,只可批準五天以內(nèi)的假期位喂,其余請求將繼續(xù)申請上級浪耘。

public class Majordomo extends Manager {

    public Majordomo(String name) {
        super(name);
    }

    @Override
    public void handlerRequest(Request request) {
        if (request.getRequestType().equals("請假") && request.getNumber() <= 5) {    //只能批準五天內(nèi)的假期
            System.out.println(name + ":" + request.getRequestContent() + ",時長:" + request.getNumber() + "天塑崖,被批準");
        } else {    //其余請求申請上級
            if (superior != null) {
                superior.handlerRequest(request);
            }
        }
    }

}

總經(jīng)理類七冲,可以批準任意時常的假期,并且可以批準是否加薪规婆。

public class GeneralManager extends Manager {

    public GeneralManager(String name) {
        super(name);
    }

    @Override
    public void handlerRequest(Request request) {
        if (request.getRequestType().equals("請假")) {    //能批準任意時長的假期
            System.out.println(name + ":" + request.getRequestContent() + "澜躺,時長:" + request.getNumber() + "天,被批準");
        } else if (request.getRequestType().equals("加薪") && request.getNumber() <= 500) {
            System.out.println(name + ":" + request.getRequestContent() + "抒蚜,金額:¥" + request.getNumber() + "掘鄙,被批準");
        } else if (request.getRequestType().equals("加薪") && request.getNumber() > 500) {
            System.out.println(name + ":" + request.getRequestContent() + ",金額:¥" + request.getNumber() + "嗡髓,再說吧");
        }
    }

}

3. 申請類

public class Request {

    private String requestType;    //申請類別
    private String requestContent;    //申請內(nèi)容
    private int number;    //數(shù)量

    public String getRequestType() {
        return requestType;
    }

    public void setRequestType(String requestType) {
        this.requestType = requestType;
    }

    public String getRequestContent() {
        return requestContent;
    }

    public void setRequestContent(String requestContent) {
        this.requestContent = requestContent;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

}

4. Client客戶端

下面測試幾組數(shù)據(jù)操漠。

public class Client {

    public static void main(String[] args) {
        CommonManager commonManager = new CommonManager("尼古拉斯·經(jīng)理");
        Majordomo majordomo = new Majordomo("尼古拉斯·總監(jiān)");
        GeneralManager generalManager = new GeneralManager("尼古拉斯·總經(jīng)理");

        //設(shè)置上級
        commonManager.setSuperior(majordomo);
        majordomo.setSuperior(generalManager);

        Request request = new Request();
        request.setRequestType("請假");
        request.setRequestContent("adam請假");
        request.setNumber(1);
        commonManager.handlerRequest(request);

        Request request2 = new Request();
        request2.setRequestType("請假");
        request2.setRequestContent("adam請假");
        request2.setNumber(4);
        commonManager.handlerRequest(request2);

        Request request3 = new Request();
        request3.setRequestType("加薪");
        request3.setRequestContent("adam請求加薪");
        request3.setNumber(500);
        commonManager.handlerRequest(request3);

        Request request4 = new Request();
        request4.setRequestType("加薪");
        request4.setRequestContent("adam請求加薪");
        request4.setNumber(1000);
        commonManager.handlerRequest(request4);
    }

}

運行結(jié)果如下:

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浊伙,更是在濱河造成了極大的恐慌撞秋,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚣鄙,死亡現(xiàn)場離奇詭異吻贿,居然都是意外死亡,警方通過查閱死者的電腦和手機哑子,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門舅列,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卧蜓,你說我怎么就攤上這事帐要。” “怎么了烦却?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵宠叼,是天一觀的道長。 經(jīng)常有香客問我其爵,道長,這世上最難降的妖魔是什么伸蚯? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任摩渺,我火速辦了婚禮,結(jié)果婚禮上剂邮,老公的妹妹穿的比我還像新娘摇幻。我一直安慰自己,他們只是感情好挥萌,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布绰姻。 她就那樣靜靜地躺著,像睡著了一般引瀑。 火紅的嫁衣襯著肌膚如雪狂芋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天憨栽,我揣著相機與錄音帜矾,去河邊找鬼。 笑死屑柔,一個胖子當著我的面吹牛屡萤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掸宛,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼死陆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唧瘾?” 一聲冷哼從身側(cè)響起措译,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤别凤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瞳遍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闻妓,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年掠械,在試婚紗的時候發(fā)現(xiàn)自己被綠了由缆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡猾蒂,死狀恐怖均唉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肚菠,我是刑警寧澤舔箭,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站蚊逢,受9級特大地震影響层扶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烙荷,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一镜会、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终抽,春花似錦戳表、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至圃郊,卻和暖如春价涝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背描沟。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工飒泻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吏廉。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓泞遗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親席覆。 傳聞我的和親對象是個殘疾皇子史辙,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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