1. 程式人生 > >責任鏈模式在專案中的實際應用(Java)

責任鏈模式在專案中的實際應用(Java)

1.責任鏈模式

首先簡單介紹一下責任鏈模式。

  1. 定義:使多個物件都有機會處理請求,從而避免了請求的傳送者和接受者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。將所有處理者形成一條鏈,在鏈中決定哪個物件能夠處理請求,並返回結果,不能處理則繼續向下傳遞請求。
  2. 優點:
    1. 將請求和處理分開,請求者不需要知道是誰處理的,處理者可以不用知道請求的全貌。
  3. 缺點:
    1. 效能問題,每個請求從鏈頭遍歷到鏈尾,如果鏈比較長則效能低下。
    2. 除錯問題,屬於遞迴呼叫,除錯不方便。
  4. 在這裡不對責任鏈模式做過多的介紹,簡單說明,然後闡述在專案中的應用以及程式碼的實現。

2.實際業務場景

在我公司的專案中,有一些需要過期處理的東西,比如,訂單未支付過期處理,發紅包未領取自動退還,代金券未支付過期處理等。實現方式都是採用我之前所寫的一篇文章,基於Redis實現訂單倒計時自動關閉——Java。在存入redis的時候,會通過key來區分是什麼業務,比如訂單的key為AA開頭,紅包的key為BB開頭,代金券的key為CC開頭。那麼,如果監聽到redis中有過期的鍵值對的話,程式會得到鍵值對的key,通過key來判斷屬於哪部分業務,然後進行相應的處理。傳統做法為,將所有的處理業務都寫在一個類,或者一個方法中,通過多個if判斷執行不同的邏輯,比如,如果是AA開頭,那麼執行訂單的過期處理邏輯等等。

這樣的做法並沒有問題,我們討論的只是一種更好的處理方式。

傳統的做法會使處理邏輯耦合在一起,變得比較臃腫,複雜,難以維護。而大部分設計模式都是為了增加程式的可維護性以及可擴充套件性,或者說解耦。

在這個場景中,應用了責任鏈模式,相當於我把每一個處理邏輯都看作是一個獨立的處理者,然後將他們形成一個處理鏈,這樣當一個請求進來之後,通過遍歷這個處理鏈來處理請求。在遍歷鏈時,通過判斷,如果能處理該請求,則處理並返回,不在繼續向下遍歷,如果不能處理則繼續向下遍歷,交個下一個處理者。

3.具體程式碼實現

通過上面的描述,我們知道,所有的處理者都是獨立的,並且每個處理者應該都具備相同的行為----處理過期邏輯,首先我通過一個抽象類,定義出抽象的處理者。

public abstract class AbstractRedisExpireHandle {

    //key的字首,用於區分是什麼業務
    private String prefix;

    //構造,子類必須實現
    public AbstractRedisExpireHandle(){}

    /**
     * 操作字首屬性的公開方法
     * @return
     */
    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    //抽象的,所有具體處理者應該實現的處理邏輯
    abstract void expireHandle(String redisKey);

}

接下來看具體的處理者,所有的具體處理者都要繼承抽象處理者,具體處理者之間是完成獨立的,解耦的。

/**
 * 商品訂單過期
 */
@Component
public class GoodsOrderExpireHandle extends AbstractRedisExpireHandle {

    //設定該業務的字首
    private static final String PREFIX = "dd";

    public GoodsOrderExpireHandle(){
        setPrefix(PREFIX);
    }

    @Reference(version = "1.0.0")
    private TaskService taskService;

    //實現具體處理邏輯
    @Override
    void expireHandle(String redisKey) {
        taskService.goodsOrderExpireHandle(redisKey);
    }
}
/**
 * 紅包過期
 */
@Component
public class RedPacketExpireHandle extends AbstractRedisExpireHandle {

    //設定業務字首
    private static final String PREFIX = "kb";

    @Reference(version = "1.0.0")
    private TaskService taskService;

    public RedPacketExpireHandle(){
        setPrefix(PREFIX);
    }

    //實現具體處理邏輯
    @Override
    void expireHandle(String redisKey) {
        taskService.redPacketExpireHandle(redisKey);
    }
}

所有的具體處理者定義完後,需要將他們組成一個處理鏈,並且進行工作,接下來看如何將所有的相關具體處理者組成一個處理鏈,因為專案是基於spring的,所以這裡使用了spring提供的幾個相關介面,並且在spring容器啟動的時候完成處理鏈的建立。

@Component
public class ExecuteHandle implements ApplicationContextAware,InitializingBean {

    //spring容器
    private ApplicationContext context;

    //具體處理者的集合
    private List<AbstractRedisExpireHandle> expireHandles = new ArrayList<>();

    //該方法會在容器啟動的時候被呼叫
    @Override
    public void afterPropertiesSet() throws Exception {
        //從容器中找到所有繼承了抽象處理者的類,並加入到集合中,從而形成處理鏈
        String[] beanNames =  BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,AbstractRedisExpireHandle.class);
        for(int i=0;i<beanNames.length;i++){
            expireHandles.add((AbstractRedisExpireHandle)context.getBean(beanNames[i]));
        }
    }

    //該方法會將spring的當前容器傳遞進來
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }


    //遍歷處理鏈,通過字首來判斷,能否處理邏輯,如果不能則繼續遍歷
    public void handle(String redisKey){
        if(expireHandles.size() > 0){
            for(AbstractRedisExpireHandle abstractRedisExpireHandle : expireHandles){
                if(redisKey.startsWith(abstractRedisExpireHandle.getPrefix())){
                    abstractRedisExpireHandle.expireHandle(redisKey);
                }
            }
        }
    }

}

上述程式碼比較簡單,該類實現了spring的兩個介面,一個是ApplicationContextAware和InitializingBean。

ApplicationContextAware為容器感知介面,實現這個介面需要實現setApplicationContext方法,spring會將容器當做引數傳遞到這個方法中,我們就可以使用當前容器了。

InitializingBean介面為初始化Bean的介面,實現這個介面需要實現afterPropertiesSet方法,該方法會在spring例項化這個類的時候被呼叫,可以做一些初始化工作,我就是在這個方法中將所有的具體處理者形成處理鏈的。

最後,通過遍歷整個處理鏈,來處理相關的邏輯,結合之前所寫的文章,使用該處理鏈處理請求的程式碼也十分簡單,只需要呼叫上面的handle方法即可。

@Component
public class RedisExpireListener implements MessageListener {

    @Autowired
    private ExecuteHandle executeHandle;

    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] body = message.getBody();
        String redisKey = new String(body);
        executeHandle.handle(redisKey);
    }
}

4.總結

這樣做之後,我們將所有具體處理者都進行了解耦,互相之間毫無關係。那麼後續如果在有新的處理邏輯需要加入進來的話,只需要繼承抽象處理者,實現相關的處理邏輯,具體處理者就可以進行工作了,無需修改其他任何程式碼。

這種方式個人覺得還是比較好的,當然肯定會有更好的處理方式,只不過目前我還沒有發掘到,大家如果有其他的好的建議可以一起討論。