Jianwoo中的設計模式(7) — 責任鏈模式

前言

想必大家都有走過流程,流程或大或小,比如有些公司用釘釘走請假流程,你請假,需要填審批人,首先是你的項目組長,然后是項目經歷,然后技術總監,然后副總,然后人事,流程可能各不一樣,但是都有共同的特點,就是這個環節中,不管誰不同意,都會使得你的流程走不下去,而你的流程是一環扣一環的往下分發,直到這個環里面的所有人都同意你才能請假成功,在任意一環斷了你都能看到是誰中斷了你的申請,像這種自己審核完了把審核往下分發的形式可以類比為責任鏈模式

責任鏈模式的應用場景

責任鏈模式在我們開發中有什么樣的應用場景呢,我想最常見的大家最頻發能看到的應該就是Android的事件分發機制了,你的容器一層套一層,你從最頂層開始觸摸,事件會不斷的往下分發,判斷條件,這就是典型的責任鏈模式
除了事件分發還有什么地方呢,我們在開發中可能有時候要過濾條件以判斷執行是否能進行下去,把不合法的參數統統攔截反饋,這也是可能用責任鏈模式來實現的,今天所講簡物中的責任鏈模式就是注冊過程中的參數過濾

簡物中的應用場景

簡物中有一個這樣的應用場景,就是注冊,剛打開注冊的時候,只顯示一個手機號碼輸入框,號碼輸入合法后,點擊下一步會有一個交互出現驗證碼輸入框,然后驗證碼輸入合法后會有一個交互出現一個密碼設置框,輸入密碼合法后可以點擊注冊,有人可能會說,這么簡單,你全部輸入完后在注冊前判斷條件不就好了?你這么說的話那你可能沒有看到,這里的注冊是分步驟的,我無法在用戶輸入手機號碼不合法的時候讓驗證碼輸入框出現,這是為了減少用戶出錯的概率,為體驗而設計,所以我通過給當前合法步驟加標記的形式來判斷我當前執行的步驟在哪里,當到了注冊步驟的時候我就執行注冊,如果不是注冊步驟,那就將當前未設置或者不合法的步驟state拋出給界面顯示

對于這種應用場景,重點是在于你對問題解決方案的考慮,可能注冊這種流程判斷參數過濾確實是一個很簡單的操作,但如果在一些別的場景,每個步驟的檢查本身就是一個耗時操作,那你還會認為每次執行都要走一下每個耗時操作的檢查直到最后一步嗎?編程思想決定了你做事繞的彎有多少,要學會考慮一類應用場景的通用解決方案

我們先對這個注冊流程簡單的畫了一個圖

簡物中的注冊流程

注意:閱讀這篇文章你需要對位運算符有一個基本的了解,可以看下Jianwoo中的小技巧 — 用位運算給一個變量多種狀態這篇文章了解一下巧用位運算來給一個變量標記不同的值

通過這個圖你應該能明白注冊流程是這樣的了,那我就不多說,我們開始設計一下責任鏈環節,按照上面的流程圖,我們應該是這樣分步驟判斷的

  • 1、添加四個過濾器,每個過濾器都有步驟標識
  • 2、從第一個過濾器開始,將當前過濾器按照流程圖指向下一個過濾器(類似鏈表形式)
  • 3、如果當前 過濾步驟&當前步驟 == 過濾步驟,說明過濾步驟以執行/正在執行,判斷是否有下一個過濾器,有的話,執行下一個過濾器的判斷,重復 3 ,如果過濾步驟&當前步驟 != 過濾步驟,說明上一步還未執行完,像外拋出上一步過濾器的state,如果當前過濾器已經通過并且沒有下一個過濾器,說明這是過濾的最后一個條件,可以注冊
  • 4、根據拋出的過濾器state來更新錯誤界面提示,如果拋出的過濾器state為最后一步的state,說明可以注冊

那我們根據以上的邏輯來設計一個責任鏈過濾器

public abstract class RegisterParamsFilter {

    RegisterParamsFilter mRegisterFilter;


    public void addNextFilter(RegisterParamsFilter registerFilter){
        mRegisterFilter = registerFilter;
    }

    public RegisterParamsFilter getRegisterFilter() {
        return mRegisterFilter;
    }

    /**
     * 判斷當前步驟是否已合法執行
     * @param preState 上個過濾器的state
     * @param state 當前的state
     * @return
     */
    public int legal(int preState, int state){
        /**
         * 當前步驟已執行/正在執行
         */
        if((state & getState()) == getState()){
            /**
             * 如果還有下個過濾器,交給下一個去判斷
             */
            if(getRegisterFilter() != null){
                return getRegisterFilter().legal(getState(), state);
            }
            /**
             * 如果沒有下一個過濾器了返回當前過濾器state
             */
            return getState();
        }
        /**
         * 當前過濾器不能執行,拋出上一個過濾器的state(此時說明上一步還未合法執行完)
         */
        return preState;
    }

    /**
     * 當前過濾器的state
     * @return
     */
    public abstract int getState();
}

那我們四個過濾器只要繼承并且設置好state和下一個過濾器的指針就可以了
手機號碼過濾器

public class PhoneFilter extends RegisterParamsFilter {

    @Override
    public int getState() {
        return RegisterState.SET_MOBILE;
    }
}

驗證碼過濾器

public class SmsFilter extends RegisterParamsFilter {

    @Override
    public int getState() {
        return RegisterState.SET_SMS_CODE;
    }
}

密碼過濾器

public class PasswordFilter extends RegisterParamsFilter {

    @Override
    public int getState() {
        return RegisterState.SET_PASSWORD;
    }
}

注冊過濾器

public class RegisterFilter extends RegisterParamsFilter {

    @Override
    public int getState() {
        return RegisterState.REGISTER;
    }

}

我們封裝一個簡單工廠來提供各種過濾器以及初始化過濾器

public class RegisterParamsFilterFactory {

    static HashMap<Integer, RegisterParamsFilter> maps;

    public static void createFilter(){
        maps = new HashMap<>();
        RegisterParamsFilter phoneFilter    = new PhoneFilter();
        RegisterParamsFilter smsFilter      = new SmsFilter();
        RegisterParamsFilter passwordFilter = new PasswordFilter();
        RegisterParamsFilter registerFilter = new RegisterFilter();

        phoneFilter.addNextFilter(smsFilter);
        smsFilter.addNextFilter(passwordFilter);
        passwordFilter.addNextFilter(registerFilter);

        maps.put(RegisterState.SET_MOBILE, phoneFilter);
        maps.put(RegisterState.SET_SMS_CODE, smsFilter);
        maps.put(RegisterState.SET_PASSWORD, passwordFilter);
        maps.put(RegisterState.REGISTER, registerFilter);
    }

    public static RegisterParamsFilter getFilter(int state){
        return maps.get(state);
    }

    public static RegisterParamsFilter getFilter(){
        return maps.get(RegisterState.SET_MOBILE);
    }

    public static void destroy(){
        maps.clear();
    }

}

createFilter方法是重點,我創建好了一個過濾器之后同時要設置下一個過濾器的指針,也就是達到我當前過濾合法了,我就交給下一個過濾器去處理的目的

好,過濾器封裝好了,首先初始化過濾器

RegisterParamsFilterFactory.createFilter();

我們在點擊下一步的地方,調用判斷

    public void clickNext(){
        int state;

        /**
         * 所有步驟均合法通過過濾器,可以注冊
         */
        if((state = RegisterParamsFilterFactory.getFilter().legal(RegisterState.SET_MOBILE, mRegisterState)) == RegisterState.REGISTER){
            handleRegister();
            return;
        }

        switch (state){
            case RegisterState.SET_MOBILE:
                /**
                 * 手機號為空
                 */
                if(isEmpty(getText(mMobile))){
                    showErrorMessageInNextButton(getString(R.string.mobile_is_empty));
                    return;
                }
                /**
                 * 手機號不合法
                 */
                if(isMobileIllegal()){
                    showErrorMessageInNextButton(getString(R.string.mobile_is_illegal));
                    return;
                }
                /**
                 * 檢查手機號是否已注冊
                 */
                handleCheckMobile();
                return;
            case RegisterState.SET_SMS_CODE:
                /**
                 * 驗證碼為空
                 */
                if(isEmpty(getText(mSmsCode))){
                    showErrorMessageInNextButton(getString(R.string.sms_code_is_empty));
                    return;
                }

                /**
                 * 驗證碼不合法
                 */
                if(isSmsCodeIllegal()){
                    showErrorMessageInNextButton(getString(R.string.sms_code_is_illegal));
                    return;
                }

                mRegisterState |= RegisterState.SET_PASSWORD;
                handleSmsCodeIsAvailable();
                break;
            case RegisterState.SET_PASSWORD:
                /**
                 * 密碼為空
                 */
                if(isEmpty(getText(mPassword))){
                    showErrorMessageInNextButton(getString(R.string.password_is_empty));
                    return;
                }
                /**
                 * 密碼不合法
                 */
                if(isPasswordIllegal()){
                    showErrorMessageInNextButton(getString(R.string.password_length_is_illegal));
                    return;
                }
                break;
            case RegisterState.REGISTER:
                break;
            default:
                break;
        }
    }

注意看到沒,如果當前步驟判斷合法,那我們可以通過位運算給當前步驟添加標記,比如在設置手機的時候手機號碼合法,可以通過以下代碼來添加下一步的標記

mRegisterState |= RegisterState.SET_SMS_CODE;

設置完了驗證碼,點擊下一步判斷驗證碼是否合法,如果合法,繼續移步到設置密碼

mRegisterState |= RegisterState.SET_PASSWORD;

那有可能人可能會問了,如果用戶設置完驗證碼,又刪除了一個怎么辦呢,很簡單,我們只要監聽驗證碼的輸入框,并且判斷驗證碼是否合法,不合法就移除設置密碼的步驟,那下次點擊就會拋出非法設置密碼,需要設置驗證碼合法才能進行密碼設置的state,怎么取消步驟呢,以下

    public void onTextChanged(int id){
        switch (id){
            case R.id.sms_code:
                if(isEmpty(getText(mSmsCode)) || isSmsCodeIllegal()){
                    /**
                     * 取消設置密碼步驟
                     */
                    mRegisterState &= ~RegisterState.SET_PASSWORD;
                }else {
                    mRegisterState |= RegisterState.SET_SMS_CODE;
                }
                handleUpdateNextText();
                break;
        }
    }

位運算給一個變量標記不同狀態是不是很方便,你可以看下Jianwoo中的小技巧 — 用位運算給一個變量多種狀態這篇文章了解如何巧用位運算來給一個變量標記不同狀態
當所有步驟都合法的時候,就可以執行注冊了,以上就是責任鏈模式的基本運用,下面放一個動圖來看下實現后的注冊效果

注冊.gif

頁面壓縮成了狗,我要正名

個人中心
注冊

喜歡這篇文章的話不要忘了給個Like!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,981評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,467評論 25 708
  • 引言: 本文系《認證鑒權與API權限控制在微服務架構中的設計與實現》系列的完結篇,前面三篇已經將認證鑒權與API權...
    aoho閱讀 3,190評論 0 16
  • 弗洛伊德對夢的解析,夢是最深層次欲望的體現和渴望,我并不排斥這個觀點,相反,第一次接觸的時候,由震驚轉為驚嘆。 從...
    057Bonnie閱讀 168評論 0 0
  • @Q17053-1湖北荊州蔡月瓊生活因?好奇心而更美妙 以后有機會分享哦! 現在我對老公是不夠愛的呢! 我們之間有...
    李文燊閱讀 189評論 0 0