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!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

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