本文首發(fā)于SegmentFault,文章地址為:https://segmentfault.com/a/1190000019241135
1 責(zé)任鏈模式現(xiàn)存缺點
由于責(zé)任鏈大多數(shù)都是不純的情況,本案例中,只要校驗失敗就直接返回,不繼續(xù)處理接下去責(zé)任鏈中的其他校驗邏輯了,故而出現(xiàn)如果某個部分邏輯是要由多個校驗器組成一個整理的校驗邏輯的話,則此責(zé)任鏈模式則顯現(xiàn)出了它的不足之處了。(責(zé)任鏈模式的具體運用以及原理請參見筆者github wiki 2 責(zé)任鏈模式)
2 改進(jìn)方式
2.1 引入適配器模式
關(guān)于接口適配器模式原理以及使用場景請參見筆者github wiki 12 適配器模式 。
2.2 引入接口默認(rèn)方法
事例代碼請參見工程
design-patterns-business中的defaultmethod包下的代碼。
2.2.1 概念
- java8引入了一個
default medthod - 使用
default關(guān)鍵字 - Spring 4.2支持加載在默認(rèn)方法里聲明的bean
2.2.2 優(yōu)點
-
用來擴展已有的接口,在對已有接口的使用不產(chǎn)生任何影響的情況下,添加擴展。
比如我們已經(jīng)投入使用的接口需要拓展一個新的方法,在Java8以前,如果為一個使用的接口增加一個新方法,則我們必須在所有實現(xiàn)類中添加該方法的實現(xiàn),否則編譯會出現(xiàn)異常。如果實現(xiàn)類數(shù)量少并且我們有權(quán)限修改,可能會工作量相對較少。如果實現(xiàn)類比較多或者我們沒有權(quán)限修改實現(xiàn)類源代碼,這樣可能就比較麻煩。而默認(rèn)方法則解決了這個問題,它提供了一個實現(xiàn),當(dāng)沒有顯示提供其他實現(xiàn)時就采用這個實現(xiàn),這樣新添加的方法將不會破壞現(xiàn)有代碼。
-
默認(rèn)方法的另一個優(yōu)勢是該方法是可選的,子類可以根據(jù)不同的需求Override默認(rèn)實現(xiàn)。
例如,我們定義一個集合接口,其中有增、刪、改等操作。如果我們的實現(xiàn)類90%都是以數(shù)組保存數(shù)據(jù),那么我們可以定義針對這些方法給出默認(rèn)實現(xiàn),而對于其他非數(shù)組集合或者有其他類似業(yè)務(wù),可以選擇性復(fù)寫接口中默認(rèn)方法。
2.2.3 使用原則
- ”類優(yōu)先” 原則
- 若一個接口中定義了一個默認(rèn)方法,而另外一個父類或接口中又定義了一個同名的方法時選擇父類中的方法:如果一個父類提供了具體的實現(xiàn),那么接口中具有相同名稱和參數(shù)的默認(rèn)方法會被忽略。
- 接口沖突原則
- 如果一個父接口提供一個默認(rèn)方法,而另一個接口也提供了一個具有相同名稱和參數(shù)列表的方法(不管方法是否是默認(rèn)方法),那么必須覆蓋該方法來解決沖突。
4.3 項目演示
3.1 邏輯梳理
由于系統(tǒng)業(yè)務(wù)需求的變更,目前有兩種業(yè)務(wù)需求,
- 一種就是按照原來的文件上傳需求,上傳過程中需要校驗操作,只要校驗失敗了,就直接返回失敗信息,整個文件都不需要做處理了。(參見之前的文章 Java設(shè)計模式綜合運用(門面+模版方法+責(zé)任鏈+策略))
- 另一種就是校驗的邏輯都一樣,但是如果校驗失敗的情況,需要繼續(xù)往下執(zhí)行,記錄一下失敗id即可,即需要把失敗的記錄也保存到數(shù)據(jù)庫中,比如約束字段不符合約束規(guī)則的情況,則把此字段置空,然后繼續(xù)執(zhí)行其他校驗器邏輯即可。(本節(jié)需要討論的問題)
3.2 實現(xiàn)方案
根據(jù)第2節(jié)的改進(jìn)方式可以知道,我們有兩種方式改進(jìn)以上邏輯。
3.2.1 采用適配器模式
代碼參見2.1版本的,地址為:https://github.com/landy8530/DesignPatterns/tree/2.1
若采用適配器模式,此處我們會采用接口適配器模式。
接口需要多增加一個不用鏈?zhǔn)秸{(diào)用的校驗方法,定義如下,
/**
* 業(yè)務(wù)校驗統(tǒng)一接口,增加了接口的默認(rèn)方法實現(xiàn),這樣可以更加方便且自由選擇實現(xiàn)接口的哪些方法。
* @author landyl
* @create 10:32 AM 05/09/2018
* @version 2.0
* @since 1.0
*/
public interface Validator<R extends RequestDetail,F extends RequestFile> {
/**
* 需要引入責(zé)任鏈的時候,則采用此方法
* @param detail
* @param chain
* @return
* @throws BusinessValidationException
*/
String doValidate(R detail, F file, ValidatorChain chain) throws BusinessValidationException;
/**
* 不需要責(zé)任鏈的時候,則可以直接調(diào)用此方法的實現(xiàn)即可
* @param detail
* @return
* @throws BusinessValidationException
*/
boolean doValidate(R detail, F file) throws BusinessValidationException;
}
適配器類定義如下抽象類,
/**
* @author: landy
* @date: 2019/5/19 14:48
* @description:
*/
public abstract class ValidatorAdapter implements Validator<RequestDetail, RequestFile> {
public String doValidate(RequestDetail detail, RequestFile file, ValidatorChain chain) throws BusinessValidationException {
boolean isValid = this.doValidate(detail, file);
//校驗失敗了,就直接返回
if(!isValid) {
if(detail.getValidationResult() != null) {
return detail.getValidationResult().getResultMsg();
}
}
//否則往責(zé)任鏈中下一個校驗器進(jìn)行處理
return chain.doValidate(detail, file);
}
@Override
public boolean doValidate(RequestDetail detail, RequestFile file) throws BusinessValidationException {
return true;
}
}
如此一來,因為增加了原有接口中的方法,則需要在每個實現(xiàn)類中都增加一個實現(xiàn)方法,雖然有以上的適配器類,但是也要把之前實現(xiàn)接口改為繼承該適配器類,即 ValidatorAdapter 類。比如,
/**
* @author landyl
* @create 2:57 PM 05/09/2018
*/
@Component(ValidatorConstants.BEAN_NAME_CUSTOMER_IS_ACTIVE)
public class IsActiveValidator extends ValidatorAdapter {
@Override
public boolean doValidate(RequestDetail detail, RequestFile file) throws BusinessValidationException {
if (StringUtils.isNotEmpty(detail.getIsActive())
&& !"0".equals(detail.getIsActive())
&& !"1".equals(detail.getIsActive())) {
String result = "An invalid Is Active setting was provided. Accepted Value(s): 0, 1 (0 = No; 1 = Yes).";
detail.bindValidationResult(Constants.INVALID_IS_ACTIVE,result);
return false;
}
return true;
}
}
而且,因為適配器類中的參數(shù)都是RequestDetail 和 RequestFile類,故而子類可能需要做強制轉(zhuǎn)換操作,如:
@Component(ValidatorConstants.BEAN_NAME_CUSTOMER_BUSINESS_LINE)
public class BusinessLineValidator extends ValidatorAdapter {
public String doValidate(RequestDetail detail, RequestFile file, ValidatorChain chain) throws BusinessValidationException {
if(detail instanceof CustomerRequestDetail) {
CustomerRequestDetail crd = (CustomerRequestDetail)detail;
String result = validateBusinessLineLogic(crd);
if(!Constants.VALID.equals(result)){
return result;
}
}
return chain.doValidate(detail,file);
}
...
}
局限性比較多。
3.2.2 采用接口默認(rèn)方法
代碼參見2.0版本,地址為:https://github.com/landy8530/DesignPatterns/tree/2.0 ,后續(xù)也會merge到master版本。
接口定義如下,采用默認(rèn)方法實現(xiàn),
/**
* 業(yè)務(wù)校驗統(tǒng)一接口,增加了接口的默認(rèn)方法實現(xiàn),這樣可以更加方便且自由選擇實現(xiàn)接口的哪些方法。
* @author landyl
* @create 10:32 AM 05/09/2018
* @version 2.0
* @since 1.0
*/
public interface Validator<R extends RequestDetail,F extends RequestFile> {
/**
* 需要引入責(zé)任鏈的時候,則采用此方法
* @param detail
* @param chain
* @return
* @throws BusinessValidationException
*/
default String doValidate(R detail, F file, ValidatorChain chain) throws BusinessValidationException {
boolean isValid = this.doValidate(detail, file);
//校驗失敗了,就直接返回
if(!isValid) {
if(detail.getValidationResult() != null) {
return detail.getValidationResult().getResultMsg();
}
}
//否則往責(zé)任鏈中下一個校驗器進(jìn)行處理
return chain.doValidate(detail, file);
}
/**
* 不需要責(zé)任鏈的時候,則可以直接調(diào)用此方法的實現(xiàn)即可
* @param detail
* @return
* @throws BusinessValidationException
*/
default boolean doValidate(R detail, F file) throws BusinessValidationException { return true; }
}
由此可見,其實此處的默認(rèn)方法,就是上節(jié)中的適配器類的實現(xiàn)方式而已,而且對于后續(xù)的實現(xiàn)類而言,無須變動(針對不需要改動業(yè)務(wù)邏輯的類而言)。即使針對需要變動業(yè)務(wù)邏輯的類,也只是改動部分而已,而且不需要類型強制轉(zhuǎn)換操作。避免了不必要的異常出現(xiàn)。
3.2.3 方案對比
經(jīng)過以上代碼實現(xiàn)可以發(fā)現(xiàn),默認(rèn)接口實現(xiàn)有其非常明顯的優(yōu)勢,即擁抱變化,擴展已有接口,向后兼容。
3.3 邏輯測試
具體的測試代碼可以參見相應(yīng)版本的github單元測試代碼即可。