背景
最近,我讓團隊內一位成員寫了一個匯入功能,他使用了責任鏈模式,代碼堆的非常多,bug 也多,沒有達到我預期的效果,
實際上,針對匯入功能,我認為模版方法更合適!為此,隔壁團隊也拿出我們的案例,進行了集體 code review,
學好設計模式,且不要為了練習,強行使用!讓原本 100 行就能實作的功能,寫了 3000 行!對錯暫且不論,我們先一起看看責任鏈設計模式吧!
什么是責任鏈
責任鏈模式是一種行為設計模式, 允許你將請求沿著處理者鏈進行發送,收到請求后, 每個處理者均可對請求進行處理, 或將其傳遞給鏈上的下個處理者,

使用場景
責任鏈的使用場景還是比較多的:
- 多條件流程判斷:權限控制
- ERP 系統流程審批:總經理、人事經理、專案經理
- Java 過濾器的底層實作 Filter
如果不使用該設計模式,那么當需求有所改變時,就會使得代碼臃腫或者難以維護,例如下面的例子,
| 反例
假設現在有一個闖關游戲,進入下一關的條件是上一關的分數要高于 xx:
- 游戲一共 3 個關卡
- 進入第二關需要第一關的游戲得分大于等于 80
- 進入第三關需要第二關的游戲得分大于等于 90
那么代碼可以這樣寫:
//第一關
public class FirstPassHandler {
public int handler(){
System.out.println("第一關-->FirstPassHandler");
return 80;
}
}
//第二關
public class SecondPassHandler {
public int handler(){
System.out.println("第二關-->SecondPassHandler");
return 90;
}
}
//第三關
public class ThirdPassHandler {
public int handler(){
System.out.println("第三關-->ThirdPassHandler,這是最后一關啦");
return 95;
}
}
//客戶端
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關
int firstScore = firstPassHandler.handler();
//第一關的分數大于等于80則進入第二關
if(firstScore >= 80){
int secondScore = secondPassHandler.handler();
//第二關的分數大于等于90則進入第二關
if(secondScore >= 90){
thirdPassHandler.handler();
}
}
}
}
那么如果這個游戲有 100 關,我們的代碼很可能就會寫成這個樣子:
if(第1關通過){
// 第2關 游戲
if(第2關通過){
// 第3關 游戲
if(第3關通過){
// 第4關 游戲
if(第4關通過){
// 第5關 游戲
if(第5關通過){
// 第6關 游戲
if(第6關通過){
//...
}
}
}
}
}
}
這種代碼不僅冗余,并且當我們要將某兩關進行調整時會對代碼非常大的改動,這種操作的風險是很高的,因此,該寫法非常糟糕,
更多設計模式系列教程:https://www.javastack.cn/
| 初步改造
如何解決這個問題,我們可以通過鏈表將每一關連接起來,形成責任鏈的方式,第一關通過后是第二關,第二關通過后是第三關....
這樣客戶端就不需要進行多重 if 的判斷了:
public class FirstPassHandler {
/**
* 第一關的下一關是 第二關
*/
private SecondPassHandler secondPassHandler;
public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
this.secondPassHandler = secondPassHandler;
}
//本關卡游戲得分
private int play(){
return 80;
}
public int handler(){
System.out.println("第一關-->FirstPassHandler");
if(play() >= 80){
//分數>=80 并且存在下一關才進入下一關
if(this.secondPassHandler != null){
return this.secondPassHandler.handler();
}
}
return 80;
}
}
public class SecondPassHandler {
/**
* 第二關的下一關是 第三關
*/
private ThirdPassHandler thirdPassHandler;
public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
this.thirdPassHandler = thirdPassHandler;
}
//本關卡游戲得分
private int play(){
return 90;
}
public int handler(){
System.out.println("第二關-->SecondPassHandler");
if(play() >= 90){
//分數>=90 并且存在下一關才進入下一關
if(this.thirdPassHandler != null){
return this.thirdPassHandler.handler();
}
}
return 90;
}
}
public class ThirdPassHandler {
//本關卡游戲得分
private int play(){
return 95;
}
/**
* 這是最后一關,因此沒有下一關
*/
public int handler(){
System.out.println("第三關-->ThirdPassHandler,這是最后一關啦");
return play();
}
}
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關
firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關的下一關是第二關
secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關的下一關是第三關
//說明:因為第三關是最后一關,因此沒有下一關
//開始呼叫第一關 每一個關卡是否進入下一關卡 在每個關卡中判斷
firstPassHandler.handler();
}
}
| 缺點
現有模式的缺點:
- 每個關卡中都有下一關的成員變數并且是不一樣的,形成鏈很不方便
- 代碼的擴展性非常不好
| 責任鏈改造
既然每個關卡中都有下一關的成員變數并且是不一樣的,那么我們可以在關卡上抽象出一個父類或者介面,然后每個具體的關卡去繼承或者實作,
有了思路,我們先來簡單介紹一下責任鏈設計模式的基本組成:
- 抽象處理者(Handler)角色:定義一個處理請求的介面,包含抽象處理方法和一個后繼連接,
- 具體處理者(Concrete Handler)角色:實作抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者,
- 客戶類(Client)角色:創建處理鏈,并向鏈頭的具體處理者物件提交請求,它不關心處理細節和請求的傳遞程序,

public abstract class AbstractHandler {
/**
* 下一關用當前抽象類來接收
*/
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract int handler();
}
public class FirstPassHandler extends AbstractHandler{
private int play(){
return 80;
}
@Override
public int handler(){
System.out.println("第一關-->FirstPassHandler");
int score = play();
if(score >= 80){
//分數>=80 并且存在下一關才進入下一關
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class SecondPassHandler extends AbstractHandler{
private int play(){
return 90;
}
public int handler(){
System.out.println("第二關-->SecondPassHandler");
int score = play();
if(score >= 90){
//分數>=90 并且存在下一關才進入下一關
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class ThirdPassHandler extends AbstractHandler{
private int play(){
return 95;
}
public int handler(){
System.out.println("第三關-->ThirdPassHandler");
int score = play();
if(score >= 95){
//分數>=95 并且存在下一關才進入下一關
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關
// 和上面沒有更改的客戶端代碼相比,只有這里的set方法發生變化,其他都是一樣的
firstPassHandler.setNext(secondPassHandler);//第一關的下一關是第二關
secondPassHandler.setNext(thirdPassHandler);//第二關的下一關是第三關
//說明:因為第三關是最后一關,因此沒有下一關
//從第一個關卡開始
firstPassHandler.handler();
}
}
| 責任鏈工廠改造
對于上面的請求鏈,我們也可以把這個關系維護到組態檔中或者一個列舉中,我將使用列舉來教會大家怎么動態的配置請求鏈并且將每個請求者形成一條呼叫鏈,

public enum GatewayEnum {
// handlerId, 攔截者名稱,全限定類名,preHandlerId,nextHandlerId
API_HANDLER(new GatewayEntity(1, "api介面限流", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),
BLACKLIST_HANDLER(new GatewayEntity(2, "黑名單攔截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),
SESSION_HANDLER(new GatewayEntity(3, "用戶會話攔截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null)),
;
GatewayEntity gatewayEntity;
public GatewayEntity getGatewayEntity() {
return gatewayEntity;
}
GatewayEnum(GatewayEntity gatewayEntity) {
this.gatewayEntity = gatewayEntity;
}
}
public class GatewayEntity {
private String name;
private String conference;
private Integer handlerId;
private Integer preHandlerId;
private Integer nextHandlerId;
}
public interface GatewayDao {
/**
* 根據 handlerId 獲取配置項
* @param handlerId
* @return
*/
GatewayEntity getGatewayEntity(Integer handlerId);
/**
* 獲取第一個處理者
* @return
*/
GatewayEntity getFirstGatewayEntity();
}
public class GatewayImpl implements GatewayDao {
/**
* 初始化,將列舉中配置的handler初始化到map中,方便獲取
*/
private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();
static {
GatewayEnum[] values = GatewayEnum.values();
for (GatewayEnum value : values) {
GatewayEntity gatewayEntity = value.getGatewayEntity();
gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
}
}
@Override
public GatewayEntity getGatewayEntity(Integer handlerId) {
return gatewayEntityMap.get(handlerId);
}
@Override
public GatewayEntity getFirstGatewayEntity() {
for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {
GatewayEntity value = https://www.cnblogs.com/javastack/p/entry.getValue();
// 沒有上一個handler的就是第一個
if (value.getPreHandlerId() == null) {
return value;
}
}
return null;
}
}
public class GatewayHandlerEnumFactory {
private static GatewayDao gatewayDao = new GatewayImpl();
// 提供靜態方法,獲取第一個handler
public static GatewayHandler getFirstGatewayHandler() {
GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
if (firstGatewayHandler == null) {
return null;
}
GatewayEntity tempGatewayEntity = firstGatewayEntity;
Integer nextHandlerId = null;
GatewayHandler tempGatewayHandler = firstGatewayHandler;
// 迭代遍歷所有handler,以及將它們鏈接起來
while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
tempGatewayHandler.setNext(gatewayHandler);
tempGatewayHandler = gatewayHandler;
tempGatewayEntity = gatewayEntity;
}
// 回傳第一個handler
return firstGatewayHandler;
}
/**
* 反射物體化具體的處理者
* @param firstGatewayEntity
* @return
*/
private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
// 獲取全限定類名
String className = firstGatewayEntity.getConference();
try {
// 根據全限定類名,加載并初始化該類,即會初始化該類的靜態段
Class<?> clazz = Class.forName(className);
return (GatewayHandler) clazz.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
public class GetewayClient {
public static void main(String[] args) {
GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}
設計模式有很多,責任鏈只是其中的一種,我覺得很有意思,非常值得一學,設計模式確實是一門藝術,仍需努力呀!
著作權宣告:本文為CSDN博主「Java小海.」的原創文章,遵循CC 4.0 BY-SA著作權協議,轉載請附上原文出處鏈接及本宣告,原文鏈接:https://blog.csdn.net/q1472750149/article/details/121886327
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
2.勁爆!Java 協程要來了,,,
3.Spring Boot 2.x 教程,太全了!
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/503059.html
標籤:Java
上一篇:C語言怎么給函式添加形參的默認值
