- 什么是責任鏈
-
場景
- 反例
- 初步改造
- 缺點
- 責任鏈改造
- 責任鏈工廠改造
- 聊聊其他
最近,我讓團隊內一位成員寫了一個導入功能。他使用了責任鏈模式,代碼堆的非常多,bug 也多,沒有達到我預期的效果。實際上,針對導入功能,我認為模版方法更合適!為此,隔壁團隊也拿出我們的案例,進行了集體 code review。
學好設計模式,且不要為了練習,強行使用!讓原本100行就能實現(xiàn)的功能,寫了 3000 行!
對錯暫且不論,我們先一起看看責任鏈設計模式吧!
什么是責任鏈
「責任鏈模式」 是一種行為設計模式, 允許你將請求沿著處理者鏈進行發(fā)送。收到請求后, 每個處理者均可對請求進行處理, 或將其傳遞給鏈上的下個處理者。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權限、多租戶、數(shù)據(jù)權限、工作流、三方登錄、支付、短信、商城等功能。
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
場景
責任鏈的使用場景還是比較多的
如果不使用該設計模式,那么當需求有所改變時,就會使得代碼臃腫或者難以維護,例如下面的例子
反例
假設現(xiàn)在有一個闖關游戲,進入下一關的條件是上一關的分數(shù)要高于xx
- 游戲一共 3 個關卡
- 進入第二關需要第一關的游戲得分大于等于 80
- 進入第三關需要第二關的游戲得分大于等于 90
那么代碼可以這樣寫
//第一關
publicclassFirstPassHandler{
publicinthandler(){
System.out.println("第一關-->FirstPassHandler");
return80;
}
}
//第二關
publicclassSecondPassHandler{
publicinthandler(){
System.out.println("第二關-->SecondPassHandler");
return90;
}
}
//第三關
publicclassThirdPassHandler{
publicinthandler(){
System.out.println("第三關-->ThirdPassHandler,這是最后一關啦");
return95;
}
}
//客戶端
publicclassHandlerClient{
publicstaticvoidmain(String[]args){
FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關
SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關
ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關
intfirstScore=firstPassHandler.handler();
//第一關的分數(shù)大于等于80則進入第二關
if(firstScore>=80){
intsecondScore=secondPassHandler.handler();
//第二關的分數(shù)大于等于90則進入第二關
if(secondScore>=90){
thirdPassHandler.handler();
}
}
}
}
那么如果這個游戲有100關,我們的代碼很可能就會寫成這個樣子
if(第1關通過){
//第2關游戲
if(第2關通過){
//第3關游戲
if(第3關通過){
//第4關游戲
if(第4關通過){
//第5關游戲
if(第5關通過){
//第6關游戲
if(第6關通過){
//...
}
}
}
}
}
}
這種代碼不僅冗余,并且當我們要將某兩關進行調整時會對代碼非常大的改動,這種操作的風險是很高的,因此,該寫法非常糟糕
初步改造
如何解決這個問題,我們可以通過鏈表將每一關連接起來,形成責任鏈的方式,第一關通過后是第二關,第二關通過后是第三關 ....,這樣客戶端就不需要進行多重 if 的判斷了
publicclassFirstPassHandler{
/**
*第一關的下一關是第二關
*/
privateSecondPassHandlersecondPassHandler;
publicvoidsetSecondPassHandler(SecondPassHandlersecondPassHandler){
this.secondPassHandler=secondPassHandler;
}
//本關卡游戲得分
privateintplay(){
return80;
}
publicinthandler(){
System.out.println("第一關-->FirstPassHandler");
if(play()>=80){
//分數(shù)>=80并且存在下一關才進入下一關
if(this.secondPassHandler!=null){
returnthis.secondPassHandler.handler();
}
}
return80;
}
}
publicclassSecondPassHandler{
/**
*第二關的下一關是第三關
*/
privateThirdPassHandlerthirdPassHandler;
publicvoidsetThirdPassHandler(ThirdPassHandlerthirdPassHandler){
this.thirdPassHandler=thirdPassHandler;
}
//本關卡游戲得分
privateintplay(){
return90;
}
publicinthandler(){
System.out.println("第二關-->SecondPassHandler");
if(play()>=90){
//分數(shù)>=90并且存在下一關才進入下一關
if(this.thirdPassHandler!=null){
returnthis.thirdPassHandler.handler();
}
}
return90;
}
}
publicclassThirdPassHandler{
//本關卡游戲得分
privateintplay(){
return95;
}
/**
*這是最后一關,因此沒有下一關
*/
publicinthandler(){
System.out.println("第三關-->ThirdPassHandler,這是最后一關啦");
returnplay();
}
}
publicclassHandlerClient{
publicstaticvoidmain(String[]args){
FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關
SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關
ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關
firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關的下一關是第二關
secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關的下一關是第三關
//說明:因為第三關是最后一關,因此沒有下一關
//開始調用第一關每一個關卡是否進入下一關卡在每個關卡中判斷
firstPassHandler.handler();
}
}
缺點
現(xiàn)有模式的缺點
-
每個關卡中都有下一關的
成員變量
并且是不一樣的,形成鏈很不方便 - 代碼的擴展性非常不好
責任鏈改造
-
既然每個關卡中都有下一關的
成員變量
并且是不一樣的,那么我們可以在關卡上抽象出一個父類或者接口,然后每個具體的關卡去繼承或者實現(xiàn)
有了思路,我們先來簡單介紹一下責任鏈設計模式的「基本組成」
- 抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
- 具體處理者(Concrete Handler)角色:實現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。
- 客戶類(Client)角色:創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關心處理細節(jié)和請求的傳遞過程。
publicabstractclassAbstractHandler{
/**
*下一關用當前抽象類來接收
*/
protectedAbstractHandlernext;
publicvoidsetNext(AbstractHandlernext){
this.next=next;
}
publicabstractinthandler();
}
publicclassFirstPassHandlerextendsAbstractHandler{
privateintplay(){
return80;
}
@Override
publicinthandler(){
System.out.println("第一關-->FirstPassHandler");
intscore=play();
if(score>=80){
//分數(shù)>=80并且存在下一關才進入下一關
if(this.next!=null){
returnthis.next.handler();
}
}
returnscore;
}
}
publicclassSecondPassHandlerextendsAbstractHandler{
privateintplay(){
return90;
}
publicinthandler(){
System.out.println("第二關-->SecondPassHandler");
intscore=play();
if(score>=90){
//分數(shù)>=90并且存在下一關才進入下一關
if(this.next!=null){
returnthis.next.handler();
}
}
returnscore;
}
}
publicclassThirdPassHandlerextendsAbstractHandler{
privateintplay(){
return95;
}
publicinthandler(){
System.out.println("第三關-->ThirdPassHandler");
intscore=play();
if(score>=95){
//分數(shù)>=95并且存在下一關才進入下一關
if(this.next!=null){
returnthis.next.handler();
}
}
returnscore;
}
}
publicclassHandlerClient{
publicstaticvoidmain(String[]args){
FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關
SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關
ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關
//和上面沒有更改的客戶端代碼相比,只有這里的set方法發(fā)生變化,其他都是一樣的
firstPassHandler.setNext(secondPassHandler);//第一關的下一關是第二關
secondPassHandler.setNext(thirdPassHandler);//第二關的下一關是第三關
//說明:因為第三關是最后一關,因此沒有下一關
//從第一個關卡開始
firstPassHandler.handler();
}
}
責任鏈工廠改造
對于上面的請求鏈,我們也可以把這個關系維護到配置文件中或者一個枚舉中。我將使用枚舉來教會大家怎么動態(tài)的配置請求鏈并且將每個請求者形成一條調用鏈。
publicenumGatewayEnum{
//handlerId,攔截者名稱,全限定類名,preHandlerId,nextHandlerId
API_HANDLER(newGatewayEntity(1,"api接口限流","cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler",null,2)),
BLACKLIST_HANDLER(newGatewayEntity(2,"黑名單攔截","cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler",1,3)),
SESSION_HANDLER(newGatewayEntity(3,"用戶會話攔截","cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler",2,null)),
;
GatewayEntitygatewayEntity;
publicGatewayEntitygetGatewayEntity(){
returngatewayEntity;
}
GatewayEnum(GatewayEntitygatewayEntity){
this.gatewayEntity=gatewayEntity;
}
}
publicclassGatewayEntity{
privateStringname;
privateStringconference;
privateIntegerhandlerId;
privateIntegerpreHandlerId;
privateIntegernextHandlerId;
}
publicinterfaceGatewayDao{
/**
*根據(jù)handlerId獲取配置項
*@paramhandlerId
*@return
*/
GatewayEntitygetGatewayEntity(IntegerhandlerId);
/**
*獲取第一個處理者
*@return
*/
GatewayEntitygetFirstGatewayEntity();
}
publicclassGatewayImplimplementsGatewayDao{
/**
*初始化,將枚舉中配置的handler初始化到map中,方便獲取
*/
privatestaticMapgatewayEntityMap=newHashMap<>();
static{
GatewayEnum[]values=GatewayEnum.values();
for(GatewayEnumvalue:values){
GatewayEntitygatewayEntity=value.getGatewayEntity();
gatewayEntityMap.put(gatewayEntity.getHandlerId(),gatewayEntity);
}
}
@Override
publicGatewayEntitygetGatewayEntity(IntegerhandlerId){
returngatewayEntityMap.get(handlerId);
}
@Override
publicGatewayEntitygetFirstGatewayEntity(){
for(Map.Entryentry:gatewayEntityMap.entrySet()){
GatewayEntityvalue=entry.getValue();
//沒有上一個handler的就是第一個
if(value.getPreHandlerId()==null){
returnvalue;
}
}
returnnull;
}
}
publicclassGatewayHandlerEnumFactory{
privatestaticGatewayDaogatewayDao=newGatewayImpl();
//提供靜態(tài)方法,獲取第一個handler
publicstaticGatewayHandlergetFirstGatewayHandler(){
GatewayEntityfirstGatewayEntity=gatewayDao.getFirstGatewayEntity();
GatewayHandlerfirstGatewayHandler=newGatewayHandler(firstGatewayEntity);
if(firstGatewayHandler==null){
returnnull;
}
GatewayEntitytempGatewayEntity=firstGatewayEntity;
IntegernextHandlerId=null;
GatewayHandlertempGatewayHandler=firstGatewayHandler;
//迭代遍歷所有handler,以及將它們鏈接起來
while((nextHandlerId=tempGatewayEntity.getNextHandlerId())!=null){
GatewayEntitygatewayEntity=gatewayDao.getGatewayEntity(nextHandlerId);
GatewayHandlergatewayHandler=newGatewayHandler(gatewayEntity);
tempGatewayHandler.setNext(gatewayHandler);
tempGatewayHandler=gatewayHandler;
tempGatewayEntity=gatewayEntity;
}
//返回第一個handler
returnfirstGatewayHandler;
}
/**
*反射實體化具體的處理者
*@paramfirstGatewayEntity
*@return
*/
privatestaticGatewayHandlernewGatewayHandler(GatewayEntityfirstGatewayEntity){
//獲取全限定類名
StringclassName=firstGatewayEntity.getConference();
try{
//根據(jù)全限定類名,加載并初始化該類,即會初始化該類的靜態(tài)段
Class>clazz=Class.forName(className);
return(GatewayHandler)clazz.newInstance();
}catch(ClassNotFoundException|IllegalAccessException|InstantiationExceptione){
e.printStackTrace();
}
returnnull;
}
}
publicclassGetewayClient{
publicstaticvoidmain(String[]args){
GetewayHandlerfirstGetewayHandler=GetewayHandlerEnumFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}
基于微服務的思想,構建在 B2C 電商場景下的項目實戰(zhàn)。核心技術棧,是 Spring Boot + Dubbo 。未來,會重構成 Spring Cloud Alibaba 。
項目地址:https://github.com/YunaiV/onemall
聊聊其他
設計模式有很多,責任鏈只是其中的一種,我覺得很有意思,非常值得一學。
設計模式確實是一門藝術,仍需努力呀
審核編輯 :李倩
-
JAVA
+關注
關注
19文章
2966瀏覽量
104703 -
代碼
+關注
關注
30文章
4780瀏覽量
68527 -
過濾器
+關注
關注
1文章
428瀏覽量
19594
原文標題:同事寫了一個責任鏈模式,bug無數(shù)!
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論