Java設計模式綜合運用(責任鏈模式進階)

1 責任鏈模式現存缺點

因爲責任鏈大多數都是不純的狀況,本案例中,只要校驗失敗就直接返回,不繼續處理接下去責任鏈中的其餘校驗邏輯了,故而出現若是某個部分邏輯是要由多個校驗器組成一個整理的校驗邏輯的話,則此責任鏈模式則顯現出了它的不足之處了。(責任鏈模式的具體運用以及原理請參見筆者github wiki 2 責任鏈模式java

2 改進方式

2.1 引入適配器模式

關於接口適配器模式原理以及使用場景請參見筆者github wiki 12 適配器模式git

2.2 引入接口默認方法

事例代碼請參見工程 design-patterns-business中的 defaultmethod包下的代碼。

2.2.1 概念

  • java8引入了一個 default medthod
  • 使用 default 關鍵字
  • Spring 4.2支持加載在默認方法裏聲明的bean

2.2.2 優勢

  • 用來擴展已有的接口,在對已有接口的使用不產生任何影響的狀況下,添加擴展。github

    好比咱們已經投入使用的接口須要拓展一個新的方法,在Java8之前,若是爲一個使用的接口增長一個新方法,則咱們必須在全部實現類中添加該方法的實現,不然編譯會出現異常。若是實現類數量少而且咱們有權限修改,可能會工做量相對較少。若是實現類比較多或者咱們沒有權限修改實現類源代碼,這樣可能就比較麻煩。而默認方法則解決了這個問題,它提供了一個實現,當沒有顯示提供其餘實現時就採用這個實現,這樣新添加的方法將不會破壞現有代碼。
  • 默認方法的另外一個優點是該方法是可選的,子類能夠根據不一樣的需求Override默認實現。數據庫

    例如,咱們定義一個集合接口,其中有增、刪、改等操做。若是咱們的實現類90%都是以數組保存數據,那麼咱們能夠定義針對這些方法給出默認實現,而對於其餘非數組集合或者有其餘相似業務,能夠選擇性複寫接口中默認方法。

2.2.3 使用原則

  • 」類優先」 原則segmentfault

    • 若一個接口中定義了一個默認方法,而另一個父類或接口中又定義了一個同名的方法時選擇父類中的方法:若是一個父類提供了具體的實現,那麼接口中具備相同名稱和參數的默認方法會被忽略。
  • 接口衝突原則設計模式

    • 若是一個父接口提供一個默認方法,而另外一個接口也提供了一個具備相同名稱和參數列表的方法(無論方法是不是默認方法),那麼必須覆蓋該方法來解決衝突。

4.3 項目演示

3.1 邏輯梳理

因爲系統業務需求的變動,目前有兩種業務需求,數組

  1. 一種就是按照原來的文件上傳需求,上傳過程當中須要校驗操做,只要校驗失敗了,就直接返回失敗信息,整個文件都不須要作處理了。(參見以前的文章 Java設計模式綜合運用(門面+模版方法+責任鏈+策略)
  2. 另外一種就是校驗的邏輯都同樣,可是若是校驗失敗的狀況,須要繼續往下執行,記錄一下失敗id便可,即須要把失敗的記錄也保存到數據庫中,好比約束字段不符合約束規則的狀況,則把此字段置空,而後繼續執行其餘校驗器邏輯便可。(本節須要討論的問題)

3.2 實現方案

根據第2節的改進方式能夠知道,咱們有兩種方式改進以上邏輯。ide

3.2.1 採用適配器模式

代碼參見2.1版本的,地址爲: https://github.com/landy8530/...

若採用適配器模式,此處咱們會採用接口適配器模式。單元測試

接口須要多增長一個不用鏈式調用的校驗方法,定義以下,測試

/**
 * 業務校驗統一接口,增長了接口的默認方法實現,這樣能夠更加方便且自由選擇實現接口的哪些方法。
 * @author landyl
 * @create 10:32 AM 05/09/2018
 * @version 2.0
 * @since 1.0
 */
public interface Validator<R extends RequestDetail,F extends RequestFile> {

    /**
     * 須要引入責任鏈的時候,則採用此方法
     * @param detail
     * @param chain
     * @return
     * @throws BusinessValidationException
     */
    String doValidate(R detail, F file, ValidatorChain chain) throws BusinessValidationException;

    /**
     * 不須要責任鏈的時候,則能夠直接調用此方法的實現便可
     * @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();
            }
        }
        //不然往責任鏈中下一個校驗器進行處理
        return chain.doValidate(detail, file);
    }

    @Override
    public boolean doValidate(RequestDetail detail, RequestFile file) throws BusinessValidationException {
        return true;
    }
}

如此一來,由於增長了原有接口中的方法,則須要在每一個實現類中都增長一個實現方法,雖然有以上的適配器類,可是也要把以前實現接口改成繼承該適配器類,即 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;
    }
}

並且,由於適配器類中的參數都是RequestDetailRequestFile類,故而子類可能須要作強制轉換操做,如:

@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 採用接口默認方法

代碼參見2.0版本,地址爲: https://github.com/landy8530/... ,後續也會merge到master版本。

接口定義以下,採用默認方法實現,

/**
 * 業務校驗統一接口,增長了接口的默認方法實現,這樣能夠更加方便且自由選擇實現接口的哪些方法。
 * @author landyl
 * @create 10:32 AM 05/09/2018
 * @version 2.0
 * @since 1.0
 */
public interface Validator<R extends RequestDetail,F extends RequestFile> {

    /**
     * 須要引入責任鏈的時候,則採用此方法
     * @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();
            }
        }
        //不然往責任鏈中下一個校驗器進行處理
        return chain.doValidate(detail, file);
    }

    /**
     * 不須要責任鏈的時候,則能夠直接調用此方法的實現便可
     * @param detail
     * @return
     * @throws BusinessValidationException
     */
    default boolean doValidate(R detail, F file) throws BusinessValidationException { return true; }

}

因而可知,其實此處的默認方法,就是上節中的適配器類的實現方式而已,並且對於後續的實現類而言,無須變更(針對不須要改動業務邏輯的類而言)。即便針對須要變更業務邏輯的類,也只是改動部分而已,並且不須要類型強制轉換操做。避免了沒必要要的異常出現。

3.2.3 方案對比

通過以上代碼實現能夠發現,默認接口實現有其很是明顯的優點,即擁抱變化,擴展已有接口,向後兼容。

3.3 邏輯測試

具體的測試代碼能夠參見相應版本的github單元測試代碼便可。

相關文章
相關標籤/搜索