<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>

          代碼越寫越亂?那是因為你沒用責(zé)任鏈

          共 22569字,需瀏覽 46分鐘

           ·

          2022-08-25 10:01


          來源:網(wǎng)絡(luò)

          • 什么是責(zé)任鏈
          • 使用場景
            • 反例
            • 初步改造
            • 缺點
            • 責(zé)任鏈改造
            • 責(zé)任鏈工廠改造
          • 結(jié)語

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

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

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

          什么是責(zé)任鏈

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


          使用場景

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

          • 多條件流程判斷:權(quán)限控制
          • ERP 系統(tǒng)流程審批:總經(jīng)理、人事經(jīng)理、項目經(jīng)理
          • Java 過濾器的底層實現(xiàn) Filter

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

          反例

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

          • 游戲一共 3 個關(guān)卡
          • 進入第二關(guān)需要第一關(guān)的游戲得分大于等于 80
          • 進入第三關(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則進入第二關(guān)
                  if(firstScore >= 80){
                      int secondScore = secondPassHandler.handler();
                      //第二關(guān)的分?jǐn)?shù)大于等于90則進入第二關(guān)
                      if(secondScore >= 90){
                          thirdPassHandler.handler();
                      }
                  }
              }
          }

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

          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)進行調(diào)整時會對代碼非常大的改動,這種操作的風(fēng)險是很高的,因此,該寫法非常糟糕。

          初步改造

          如何解決這個問題,我們可以通過鏈表將每一關(guān)連接起來,形成責(zé)任鏈的方式,第一關(guān)通過后是第二關(guān),第二關(guān)通過后是第三關(guā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)才進入下一關(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)才進入下一關(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)

                  //說明:因為第三關(guān)是最后一關(guān),因此沒有下一關(guān)
                  //開始調(diào)用第一關(guān) 每一個關(guān)卡是否進入下一關(guān)卡 在每個關(guān)卡中判斷
                  firstPassHandler.handler();

              }
          }

          缺點

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

          • 每個關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,形成鏈很不方便
          • 代碼的擴展性非常不好

          責(zé)任鏈改造

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

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

          • 抽象處理者(Handler)角色: 定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
          • 具體處理者(Concrete Handler)角色: 實現(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)才進入下一關(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)才進入下一關(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)才進入下一關(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)

                  //說明:因為第三關(guān)是最后一關(guān),因此沒有下一關(guān)

                  //從第一個關(guān)卡開始
                  firstPassHandler.handler();

              }
          }

          責(zé)任鏈工廠改造

          對于上面的請求鏈,我們也可以把這個關(guān)系維護到配置文件中或者一個枚舉中。我將使用枚舉來教會大家怎么動態(tài)的配置請求鏈并且將每個請求者形成一條調(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"用戶會話攔截""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 獲取配置項
               * @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 = entry.getValue();
                      //  沒有上一個handler的就是第一個
                      if (value.getPreHandlerId() == null) {
                          return value;
                      }
                  }
                  return null;
              }
          }

          public class GatewayHandlerEnumFactory {

              private static GatewayDao gatewayDao = new GatewayImpl();

              // 提供靜態(tài)方法,獲取第一個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 {
                      // 根據(jù)全限定類名,加載并初始化該類,即會初始化該類的靜態(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è)計模式有很多,責(zé)任鏈只是其中的一種,我覺得很有意思,非常值得一學(xué)。設(shè)計模式確實是一門藝術(shù),仍需努力呀!

          程序汪資料鏈接

          程序汪接的7個私活都在這里,經(jīng)驗整理

          Java項目分享  最新整理全集,找項目不累啦 07版

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!

          歡迎添加程序汪個人微信 itwang009  進粉絲群或圍觀朋友圈

          瀏覽 34
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  人人干人人撸 | 大几把久久 | 亚洲秘 无码一区二区三区妃光 | аⅴ资源新版在线天堂 | 欧美性爱在线播放 |