天天看點

昨天阿裡學長寫了一個責任鍊模式,竟然出現了無數個bug

作者:程式猿阿嘴

目錄

  • 背景
  • 什麼是責任鍊
  • 使用場景
  • 結語

背景

最近,我讓團隊内一位成員寫了一個導入功能。他使用了責任鍊模式,代碼堆得非常多,bug 也很多,沒有達到我預期的效果。

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

學好設計模式,且不要為了練習,強行使用!讓原本 100 行就能實作的功能,寫了 3000 行!對此暫且不論,我們先一起看看責任鍊的設計模式吧!

什麼是責任鍊

責任鍊模式是一種行為設計模式, 允許你将請求沿着處理者連結進行發送。收到請求後, 每個處理者均可對請求進行處理, 或将其傳遞給鍊上的下個處理者。

昨天阿裡學長寫了一個責任鍊模式,竟然出現了無數個bug

圖檔

使用場景

責任鍊的使用場景還是比較多的:

  • 多條件流程判斷:權限控制
  • 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關通過){  
                        //...  
                    }  
                }  
            }   
        }  
    }  
}  
           

這種代碼不僅備援,并且當我們要将某兩關進行調整時會對代碼非常大的改動,這種操作的風險是很高的,是以,該寫法非常糟糕。

| 初步改造

如何解決這個問題,我們可以通過連結清單将每一關連接配接起來,形成責任鍊的方式,第一關通過後是第二關,第二關通過後是第三關....

這樣用戶端就不需要進行多重 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)角色: 建立處理鍊,并向鍊頭的具體處理者對象送出請求,它不關心處理細節和請求的傳遞過程。
昨天阿裡學長寫了一個責任鍊模式,竟然出現了無數個bug

圖檔

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();  
  
    }  
}  
           

| 責任鍊工廠改造

對于上面的請求鍊,我們也可以把這個關系維護到配置檔案中或者一個枚舉中。我将使用枚舉來教會大家怎麼動态的配置請求鍊并且将每個請求者形成一條調用鍊。

昨天阿裡學長寫了一個責任鍊模式,竟然出現了無數個bug

圖檔

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 = 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();  
    }  
}  
           

結語

設計模式有很多,責任鍊隻是其中的一種,我覺得很有意思,非常值得一學。設計模式确實是一門藝術,仍需努力呀!

來源:blog.csdn.net/q1472750149/article/details/121886327