責任鏈模式在專案中的實際應用(Java)
1.責任鏈模式
首先簡單介紹一下責任鏈模式。
- 定義:使多個物件都有機會處理請求,從而避免了請求的傳送者和接受者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。將所有處理者形成一條鏈,在鏈中決定哪個物件能夠處理請求,並返回結果,不能處理則繼續向下傳遞請求。
- 優點:
- 將請求和處理分開,請求者不需要知道是誰處理的,處理者可以不用知道請求的全貌。
- 缺點:
- 效能問題,每個請求從鏈頭遍歷到鏈尾,如果鏈比較長則效能低下。
- 除錯問題,屬於遞迴呼叫,除錯不方便。
- 在這裡不對責任鏈模式做過多的介紹,簡單說明,然後闡述在專案中的應用以及程式碼的實現。
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.總結
這樣做之後,我們將所有具體處理者都進行了解耦,互相之間毫無關係。那麼後續如果在有新的處理邏輯需要加入進來的話,只需要繼承抽象處理者,實現相關的處理邏輯,具體處理者就可以進行工作了,無需修改其他任何程式碼。
這種方式個人覺得還是比較好的,當然肯定會有更好的處理方式,只不過目前我還沒有發掘到,大家如果有其他的好的建議可以一起討論。