1. 程式人生 > 實用技巧 >Java設計模式十九——責任鏈模式

Java設計模式十九——責任鏈模式

責任鏈模式

老李的苦惱

每個人在出生的時候,都早已在暗中被標好了三六九等。

老李是一名建築工地的木匠,和大多數生活在社會最底層的農民工一樣,一輩子老實本分,膽小怕事。在他們的心中,誰當老爺都沒有區別,世界發展如何也與他們無關,只要包工頭能按時發工資,只要小家平安無事就夠了,平時受點欺負,累點苦點也沒辦法,能忍則忍了。

並不是你安分守己,世界就會給你公平公正,社會它牢記著你的出身。

老李最近遇到了一件糟心事,起因是在工地幹活的時候,被鋼管砸斷了鼻樑骨和顴骨,流了一個多小時血,才送到醫院,包工頭交了醫療費後就不再過問,老李在醫院做手術加恢復總共就住了一個星期,還在流鼻血就匆匆出院,理由是怕給包工頭添麻煩。


出院後一直頭昏流鼻血也自己扛著,期間包工頭不管不問老李身體無法支撐他出門,態度惡劣甚至辱罵讓他去送材料到公司讓公司報銷,不送去就再也不管他。當老李問及賠償事宜怎麼處理時,包工頭雲淡風輕的說人現在又沒事,給個萬把塊不就好了,老李一輩子太膽小懦弱了,不會又不敢維權,怕包工頭前年和去年拖欠的工資不給自己(包工頭沒打欠條),自己一個人在家生悶氣,好幾天不吃飯。

會哭的孩子有糖吃,聽話的孩子去奔湧吧。

老李就是這樣的一個人,一輩子默默的吃著悶虧,遇事只知道生悶氣,懲罰自己然後勸說自己妥協。這樣的心態絕不止老李一個人,自古以來我們的老百姓就是這樣,因為出身賤民,因為怕給任何人帶去麻煩,因為經不起磨難,因為這樣的人最好管理,因為你的出身就是你的原罪。

因為我曾經看見過光,所以我再也不怕黑暗。

我就是想在每篇文章的開頭寫一些人和事,想表達一些可以值得思考的東西。老李的家人曾經去過專案部找公司,也找過包工頭,但是都沒有人處理。不管哪個社會,沒人沒錢都不好辦事,現實且殘忍,如果沒有關係,老李要麼繼續熟練的忍氣吞聲,要麼自己一直找公司或者包工頭糾纏無果。

問題分析

  1. 社會現實角度:老李吃了那麼大的虧,現在公司和包工頭都不管不問,相互踢皮球,而老李又軟弱慣了,無可奈何。要怎麼解決這個問題呢?老李有個親戚老趙聽到此事非常氣憤,老實人就要任人宰割嗎?老趙利用自己的人脈關係直接找到了公司的大老闆,大老闆說這事包在他身上,他讓專案經理去處理,不用老李在來回跑了,等處理結果就好了。
  2. 我們再從技術角度分析一下,一是老李不知道具體找誰處理工傷事故,也就是找不到處理他賠償請求的責任人,另一個就是老李需要不斷的和公司或者包工頭糾纏,也就是耦合性非常高,而這兩點都違背了我們日常開發的設計原則。

這個時候,我們本文的重點,責任鏈模式就正式登場了。

責任鏈模式

什麼是責任鏈模式?

責任鏈模式(Chain of Responsibility):使多個物件都有機會去處理請求。從而避免請求的傳送者和接收者之間的耦合關係。將這個物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。

責任鏈模式也叫職責鏈模式,屬於行為型模式。通過責任鏈模式,可以為某個請求建立一個物件鏈。每個物件按照順序檢查該請求,並處理該請求,或者傳給鏈中的下一個物件處理。

責任鏈模式的三個角色

責任鏈模式主要有以下三個角色:

  • 抽象處理者角色(Handler):定義一個處理請求的介面,內部包含一個後繼處理者的引用和一個處理請求的方法,抽象處理者可以是一個介面或者抽象類。
  • 具體處理者角色(ConcreteHandler):實現抽象處理者。如果能夠處理請求則處理,否則就將該請求轉發給它的後繼處理者。在具體處理者中可以訪問鏈中下一個物件,以便請求的轉發。
  • 請求傳送者角色:建立處理鏈,同時向鏈頭的具體處理者物件提交請求,它並不關心處理細節和請求的傳遞過程。

責任鏈UML圖

程式碼例項

上文中我們知道老李的親戚老趙出面才擺平了此事,那老趙是怎麼解決的呢?老趙找到公司的大老闆,大老闆讓專案經理處理,專案經理讓包工頭或者其他人配合處理,這就形成了一個物件責任鏈,總有一個物件處理賠償的請求,而老李再也不用關心具體是誰處理的工傷賠償的請求。

下面我們用程式碼來演示一遍。

1、編寫抽象處理者角色Handler

package com.mazhichu.designpatterns.responsibility;

import java.math.BigDecimal;

/**
* @ClassName: DealWorkInjuryRequestHandler
* @Description: 抽象處理者角色
* @Author: Moore
* @Date: 2020-06-29 9:39
* @Version: V1.0
*/
public abstract class DealWorkInjuryRequestHandler { /**
* 如果處理不了,設定後繼處理者
*/
protected DealWorkInjuryRequestHandler successor; /**
* 處理請求的方法
* @param compensationPayments
*/
public abstract void dealWorkInjury(BigDecimal compensationPayments); public void setSuccessor(DealWorkInjuryRequestHandler successor) {
this.successor = successor;
}
}

2、編寫具體處理者角色老李的親戚老趙。

package com.mazhichu.designpatterns.responsibility;

import java.math.BigDecimal;

/**
* @ClassName: LaoZhaoHandler
* @Description: 具體處理者角色:老趙
* @Author: Moore
* @Date: 2020-06-29 9:40
* @Version: V1.0
*/
public class LaoZhaoHandler extends DealWorkInjuryRequestHandler{
@Override
public void dealWorkInjury(BigDecimal compensationPayments) {
System.out.println("老趙:老李別擔心,賠償款一共"+compensationPayments+"元,我已經找了他們大老闆去處理了。");
successor.dealWorkInjury(compensationPayments);
}
}

3、編寫具體處理者角色承建公司大老闆。

package com.mazhichu.designpatterns.responsibility;

import java.math.BigDecimal;

/**
* @ClassName: LaoZhaoHandler
* @Description: 具體處理者角色:承建公司大老闆
* @Author: Moore
* @Date: 2020-06-29 9:40
* @Version: V1.0
*/
public class BossHandler extends DealWorkInjuryRequestHandler{
@Override
public void dealWorkInjury(BigDecimal compensationPayments) {
if(compensationPayments.compareTo(BigDecimal.valueOf(100000))>0){
System.out.println("大老闆"+compensationPayments+"賠償款,先拖拖,找人也不行。");
}else {
System.out.println("大老闆"+compensationPayments+"賠償款,才這點錢,讓專案經理去處理。");
successor.dealWorkInjury(compensationPayments);
}
}
}

4、編寫具體處理者角色包工頭

package com.mazhichu.designpatterns.responsibility;

import java.math.BigDecimal;
import java.math.MathContext; /**
* @ClassName: LaoZhaoHandler
* @Description: 具體處理者角色:承建公司包工頭
* @Author: Moore
* @Date: 2020-06-29 9:40
* @Version: V1.0
*/
public class ContractorHandler extends DealWorkInjuryRequestHandler{
@Override
public void dealWorkInjury(BigDecimal compensationPayments) {
if(compensationPayments.compareTo(BigDecimal.valueOf(30000))>0){
System.out.println("包工頭:上面壓我,倒黴玩意,"+compensationPayments+"賠償款,我要賠"+compensationPayments.multiply(BigDecimal.valueOf(0.3), MathContext.DECIMAL32));
}else {
System.out.println("包工頭:上面壓我,倒黴玩意,我要自己掏"+compensationPayments+"賠償款。");
}
}
}

5、我們看看老李的維權之路是怎樣的,編寫客戶端請求傳送者角色老李。

package com.mazhichu.designpatterns.responsibility;

import java.math.BigDecimal;

/**
* @ClassName: Laoli
* @Description: 請求傳送者角色:老李
* @Author: Moore
* @Date: 2020-06-29 10:44
* @Version: V1.0
*/
public class Laoli { public static void main(String[] args) {
DealWorkInjuryRequestHandler lzHandler = new LaoZhaoHandler();
DealWorkInjuryRequestHandler bossHandler = new BossHandler();
DealWorkInjuryRequestHandler manageHandler = new ManageHandler();
DealWorkInjuryRequestHandler contractorHandler = new ContractorHandler();
lzHandler.setSuccessor(bossHandler);
bossHandler.setSuccessor(manageHandler);
manageHandler.setSuccessor(contractorHandler);
lzHandler.dealWorkInjury(BigDecimal.valueOf(120000));
System.out.println("-------------------------------------------------\n");
lzHandler.dealWorkInjury(BigDecimal.valueOf(80000));
System.out.println("-------------------------------------------------\n");
lzHandler.dealWorkInjury(BigDecimal.valueOf(20000));
}
}

6、檢視執行結果:

可以看出,在程式碼例項中我們建立了四個處理者物件,並指定第一個處理者物件的下家是第二個處理者物件,一直到第四個處理物件包工頭為止。然後客戶端老李將請求傳遞給第一個處理者物件老趙,老李不用再關心具體是誰處理了工傷賠償。他只知道,如果沒有老趙,他最終可能只能認命,聽話不鬧事就能拿到工資就好。

老李是太膽小又怕事,其實他完全可以拿著傷殘鑑定去勞動局監察大隊去投訴,這樣就會在責任鏈上多一個具體處理者角色,監察大隊會繼續追查下去,找到鏈上該負責的處理物件,環環相扣,最終給老李一個交代。

總結

熟悉資料結構的可以想象一下連結串列的結構,每一個節點都有一個指向後繼節點的引用。責任鏈模式在我們的日常生活中也很常見,比如常見的報銷或者請假流程,客戶端只需要將請求提交到鏈上,就不用關心是哪個具體處理物件處理了請求。也可以動態的增加鏈上的具體處理角色,只要指定後繼者處理物件就好。

適用場景

  • 一個請求的處理需要多個物件當中的一個或者幾個協作處理,具體哪個物件處理該請求由執行時刻自動確定。
  • 在不明確指定接收者的情況下,向多個物件中的一個提交一個請求。
  • 可動態指定一組物件處理請求。

責任鏈模式優點

  • 將請求的傳送者和接收者解耦。
  • 簡化了物件。使得物件不需要知道鏈的結構。
  • 通過改變責任鏈內的物件或者調動責任鏈的順序,允許動態增加或者刪除責任鏈物件。
  • 在系統中增加一個新的具體請求處理者時無須修改原有系統的程式碼,只需要在客戶端重新建鏈即可,從這一點來看是符合“開閉原則”的。

責任鏈模式缺點

  • 責任鏈在處理請求時,可能會耗時過長。
  • 責任鏈較長時,物件可能會涉及多個,從而影響系統執行效能,消耗記憶體。
  • 並不保證請求一定會被執行,如果指定後繼處理者不當,可能會造成死迴圈。

純的與不純的責任鏈模式

一個純的責任鏈模式要求一個具體的處理者物件只能在兩個行為中選擇一個:一是承擔責任,而是把責任推給下家。不允許出現某一個具體處理者物件在承擔了一部分責任後又把責任向下傳的情況。

在一個純的責任鏈模式裡面,一個請求必須被某一個處理者物件所接收;在一個不純的責任鏈模式裡面,一個請求可以最終不被任何接收端物件所接收。

純的責任鏈模式的實際例子很難找到,一般看到的例子均是不純的責任鏈模式的實現。

後話

責任鏈模式就講解到這,其實很簡單很好理解。難以理解的是其實老李到現在還沒有處理好他的工傷賠償事宜,不知道何時,農民工才能真正受到這個社會的尊重,不知道何時,我們才能驕傲的說我們是一個法治社會。讓所有人有法可依,有理可證,有平等和公平可言。

如果可以更好,請給我光明。

如果不能更好,希望別太壞。

小驚喜

認識我的或者關注【碼之初】有一段時間的朋友都知道,我是個與世無爭的完全自己打理的小號主,能力一般,技術有限,堅持更新,只因熱愛。沒有精力和財力推廣,所以也不曾盈利過,但因為同樣的熱愛,我們在此相遇,為了感恩這份相遇,我決定,從2020年7月1日開始,每半個月查一下後臺資料,閱讀最多的前三名,每人一個茶葉蛋,分享文章的前三名,依次為紅牛、脈動、農夫山泉,每個月1號、16號公佈結果並折現發給這些陪伴碼之初的朋友,經濟蕭條,金額較小,承蒙不棄,皆為心意。