<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          同事寫了一個(gè)X鏈模式,bug無數(shù)...

          共 27112字,需瀏覽 55分鐘

           ·

          2022-06-21 17:07

          點(diǎn)擊上方[全棧開發(fā)者社區(qū)]右上角[...][設(shè)為星標(biāo)?

          點(diǎn)擊領(lǐng)取全棧資料全棧資料

          目錄

          • 背景
          • 什么是責(zé)任鏈
          • 使用場景
          • 結(jié)語

          背景

          最近,我讓團(tuán)隊(duì)內(nèi)一位成員寫了一個(gè)導(dǎo)入功能。他使用了責(zé)任鏈模式,代碼堆的非常多,bug 也多,沒有達(dá)到我預(yù)期的效果。

          實(shí)際上,針對導(dǎo)入功能,我認(rèn)為模版方法更合適!為此,隔壁團(tuán)隊(duì)也拿出我們的案例,進(jìn)行了集體 code review。

          學(xué)好設(shè)計(jì)模式,且不要為了練習(xí),強(qiáng)行使用!讓原本 100 行就能實(shí)現(xiàn)的功能,寫了 3000 行!對錯(cuò)暫且不論,我們先一起看看責(zé)任鏈設(shè)計(jì)模式吧!

          什么是責(zé)任鏈

          責(zé)任鏈模式是一種行為設(shè)計(jì)模式, 允許你將請求沿著處理者鏈進(jìn)行發(fā)送。收到請求后, 每個(gè)處理者均可對請求進(jìn)行處理, 或?qū)⑵鋫鬟f給鏈上的下個(gè)處理者。

          圖片

          使用場景

          責(zé)任鏈的使用場景還是比較多的:

          • 多條件流程判斷:權(quán)限控制

          • ERP 系統(tǒng)流程審批:總經(jīng)理、人事經(jīng)理、項(xiàng)目經(jīng)理

          • Java 過濾器的底層實(shí)現(xiàn) Filter

          如果不使用該設(shè)計(jì)模式,那么當(dāng)需求有所改變時(shí),就會(huì)使得代碼臃腫或者難以維護(hù),例如下面的例子。

          | 反例

          假設(shè)現(xiàn)在有一個(gè)闖關(guān)游戲,進(jìn)入下一關(guān)的條件是上一關(guān)的分?jǐn)?shù)要高于 xx:

          • 游戲一共 3 個(gè)關(guān)卡

          • 進(jìn)入第二關(guān)需要第一關(guān)的游戲得分大于等于 80

          • 進(jìn)入第三關(guān)需要第二關(guān)的游戲得分大于等于 90

          那么代碼可以這樣寫:

          //第一關(guān)  
          public class FirstPassHandler {  
              public int handler(){  
                  System.out.println("第一關(guān)-->FirstPassHandler");  
                  return 80;  
              }  
          }  
            
          //第二關(guān)  
          public class SecondPassHandler {  
              public int handler(){  
                  System.out.println("第二關(guān)-->SecondPassHandler");  
                  return 90;  
              }  
          }  
            
            
          //第三關(guān)  
          public class ThirdPassHandler {  
              public int handler(){  
                  System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦");  
                  return 95;  
              }  
          }  
            
            
          //客戶端  
          public class HandlerClient {  
              public static void main(String[] args) {  
            
                  FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關(guān)  
                  SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關(guān)  
                  ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關(guān)  
            
                  int firstScore = firstPassHandler.handler();  
                  //第一關(guān)的分?jǐn)?shù)大于等于80則進(jìn)入第二關(guān)  
                  if(firstScore >= 80){  
                      int secondScore = secondPassHandler.handler();  
                      //第二關(guān)的分?jǐn)?shù)大于等于90則進(jìn)入第二關(guān)  
                      if(secondScore >= 90){  
                          thirdPassHandler.handler();  
                      }  
                  }  
              }  
          }  

          那么如果這個(gè)游戲有 100 關(guān),我們的代碼很可能就會(huì)寫成這個(gè)樣子:

          if(第1關(guān)通過){  
              // 第2關(guān) 游戲  
              if(第2關(guān)通過){  
                  // 第3關(guān) 游戲  
                  if(第3關(guān)通過){  
                     // 第4關(guān) 游戲  
                      if(第4關(guān)通過){  
                          // 第5關(guān) 游戲  
                          if(第5關(guān)通過){  
                              // 第6關(guān) 游戲  
                              if(第6關(guān)通過){  
                                  //...  
                              }  
                          }  
                      }   
                  }  
              }  
          }  

          這種代碼不僅冗余,并且當(dāng)我們要將某兩關(guān)進(jìn)行調(diào)整時(shí)會(huì)對代碼非常大的改動(dòng),這種操作的風(fēng)險(xiǎn)是很高的,因此,該寫法非常糟糕。

          | 初步改造

          如何解決這個(gè)問題,我們可以通過鏈表將每一關(guān)連接起來,形成責(zé)任鏈的方式,第一關(guān)通過后是第二關(guān),第二關(guān)通過后是第三關(guān)....

          這樣客戶端就不需要進(jìn)行多重 if 的判斷了:

          public class FirstPassHandler {  
              /**  
               * 第一關(guān)的下一關(guān)是 第二關(guān)  
               */
            
              private SecondPassHandler secondPassHandler;  
            
              public void setSecondPassHandler(SecondPassHandler secondPassHandler) {  
                  this.secondPassHandler = secondPassHandler;  
              }  
            
              //本關(guān)卡游戲得分  
              private int play(){  
                  return 80;  
              }  
            
              public int handler(){  
                  System.out.println("第一關(guān)-->FirstPassHandler");  
                  if(play() >= 80){  
                      //分?jǐn)?shù)>=80 并且存在下一關(guān)才進(jìn)入下一關(guān)  
                      if(this.secondPassHandler != null){  
                          return this.secondPassHandler.handler();  
                      }  
                  }  
            
                  return 80;  
              }  
          }  
            
          public class SecondPassHandler {  
            
              /**  
               * 第二關(guān)的下一關(guān)是 第三關(guān)  
               */
            
              private ThirdPassHandler thirdPassHandler;  
            
              public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {  
                  this.thirdPassHandler = thirdPassHandler;  
              }  
            
              //本關(guān)卡游戲得分  
              private int play(){  
                  return 90;  
              }  
            
              public int handler(){  
                  System.out.println("第二關(guān)-->SecondPassHandler");  
            
                  if(play() >= 90){  
                      //分?jǐn)?shù)>=90 并且存在下一關(guān)才進(jìn)入下一關(guān)  
                      if(this.thirdPassHandler != null){  
                          return this.thirdPassHandler.handler();  
                      }  
                  }  
            
                  return 90;  
              }  
          }  
            
          public class ThirdPassHandler {  
            
              //本關(guān)卡游戲得分  
              private int play(){  
                  return 95;  
              }  
            
              /**  
               * 這是最后一關(guān),因此沒有下一關(guān)  
               */
            
              public int handler(){  
                  System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦");  
                  return play();  
              }  
          }  
            
          public class HandlerClient {  
              public static void main(String[] args) {  
            
                  FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關(guān)  
                  SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關(guān)  
                  ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關(guān)  
            
                  firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān)  
                  secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān)  
            
                  //說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān)  
                  //開始調(diào)用第一關(guān) 每一個(gè)關(guān)卡是否進(jìn)入下一關(guān)卡 在每個(gè)關(guān)卡中判斷  
                  firstPassHandler.handler();  
            
              }  
          }  

          | 缺點(diǎn)

          現(xiàn)有模式的缺點(diǎn):

          • 每個(gè)關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,形成鏈很不方便

          • 代碼的擴(kuò)展性非常不好

          | 責(zé)任鏈改造

          既然每個(gè)關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,那么我們可以在關(guān)卡上抽象出一個(gè)父類或者接口,然后每個(gè)具體的關(guān)卡去繼承或者實(shí)現(xiàn)。

          有了思路,我們先來簡單介紹一下責(zé)任鏈設(shè)計(jì)模式的基本組成:

          • 抽象處理者(Handler)角色: 定義一個(gè)處理請求的接口,包含抽象處理方法和一個(gè)后繼連接。

          • 具體處理者(Concrete Handler)角色: 實(shí)現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者。

          • 客戶類(Client)角色: 創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細(xì)節(jié)和請求的傳遞過程。

          圖片
          public abstract class AbstractHandler {  
            
              /**  
               * 下一關(guān)用當(dāng)前抽象類來接收  
               */
            
              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("第一關(guān)-->FirstPassHandler");  
                  int score = play();  
                  if(score >= 80){  
                      //分?jǐn)?shù)>=80 并且存在下一關(guān)才進(jìn)入下一關(guān)  
                      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("第二關(guān)-->SecondPassHandler");  
            
                  int score = play();  
                  if(score >= 90){  
                      //分?jǐn)?shù)>=90 并且存在下一關(guān)才進(jìn)入下一關(guān)  
                      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("第三關(guān)-->ThirdPassHandler");  
                  int score = play();  
                  if(score >= 95){  
                      //分?jǐn)?shù)>=95 并且存在下一關(guān)才進(jìn)入下一關(guān)  
                      if(this.next != null){  
                          return this.next.handler();  
                      }  
                  }  
                  return score;  
              }  
          }  
            
          public class HandlerClient {  
              public static void main(String[] args) {  
            
                  FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關(guān)  
                  SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關(guān)  
                  ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關(guān)  
            
                  // 和上面沒有更改的客戶端代碼相比,只有這里的set方法發(fā)生變化,其他都是一樣的  
                  firstPassHandler.setNext(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān)  
                  secondPassHandler.setNext(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān)  
            
                  //說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān)  
            
                  //從第一個(gè)關(guān)卡開始  
                  firstPassHandler.handler();  
            
              }  
          }  

          | 責(zé)任鏈工廠改造

          對于上面的請求鏈,我們也可以把這個(gè)關(guān)系維護(hù)到配置文件中或者一個(gè)枚舉中。我將使用枚舉來教會(huì)大家怎么動(dòng)態(tài)的配置請求鏈并且將每個(gè)請求者形成一條調(diào)用鏈。

          圖片
          public enum GatewayEnum {  
              // handlerId, 攔截者名稱,全限定類名,preHandlerId,nextHandlerId  
              API_HANDLER(new GatewayEntity(1"api接口限流""cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler"null2)),  
              BLACKLIST_HANDLER(new GatewayEntity(2"黑名單攔截""cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler"13)),  
              SESSION_HANDLER(new GatewayEntity(3"用戶會(huì)話攔截""cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler"2null)),  
              ;  
            
              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 {  
            
              /**  
               * 根據(jù) handlerId 獲取配置項(xiàng)  
               * @param handlerId  
               * @return  
               */
            
              GatewayEntity getGatewayEntity(Integer handlerId);  
            
              /**  
               * 獲取第一個(gè)處理者  
               * @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 = entry.getValue();  
                      //  沒有上一個(gè)handler的就是第一個(gè)  
                      if (value.getPreHandlerId() == null) {  
                          return value;  
                      }  
                  }  
                  return null;  
              }  
          }  
            
          public class GatewayHandlerEnumFactory {  
            
              private static GatewayDao gatewayDao = new GatewayImpl();  
            
              // 提供靜態(tài)方法,獲取第一個(gè)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;  
                  }  
              // 返回第一個(gè)handler  
                  return firstGatewayHandler;  
              }  
            
              /**  
               * 反射實(shí)體化具體的處理者  
               * @param firstGatewayEntity  
               * @return  
               */
            
              private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {  
                  // 獲取全限定類名  
                  String className = firstGatewayEntity.getConference();   
                  try {  
                      // 根據(jù)全限定類名,加載并初始化該類,即會(huì)初始化該類的靜態(tài)段  
                      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();  
              }  
          }  

          結(jié)語

          設(shè)計(jì)模式有很多,責(zé)任鏈只是其中的一種,我覺得很有意思,非常值得一學(xué)。設(shè)計(jì)模式確實(shí)是一門藝術(shù),仍需努力呀!

          覺得本文對你有幫助?請分享給更多人

          關(guān)注「全棧開發(fā)者社區(qū)」加星標(biāo),提升全棧技能

          本公眾號會(huì)不定期給大家發(fā)福利,包括送書、學(xué)習(xí)資源等,敬請期待吧!

          如果感覺推送內(nèi)容不錯(cuò),不妨右下角點(diǎn)個(gè)在看轉(zhuǎn)發(fā)朋友圈或收藏,感謝支持。

          好文章,留言、點(diǎn)贊、在看和分享一條龍


          瀏覽 31
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲高清无码免费视频 | 成人网av影音 | 国产ww| 天天爽天天爽夜夜爽 | 一区二区三区四区精品视频 |