簡化分支判斷的設計模式


  很多時候會發現自己在寫代碼的時候寫了一坨if else 語句使得自己的代碼看起來很丑,隨着業務量的增大,代碼變得很難維護,之前想到能替換if else的只有switch,其實效果並沒有明顯的提升,現在在看設計模式方面的知識,發現兩種設計模式能夠解決分支判斷的臃腫問題。

狀態模式

使用場景

  大家都知道超級瑪麗的游戲吧,瑪麗要吃蘑菇,他就要挑起,頂出牆壁里的蘑菇;瑪麗想到懸崖的另一邊,他就要跳起;瑪麗想躲避被前面的烏龜咬到,他就要開槍打死烏龜;前面飛過炮彈,瑪麗就要蹲下躲避;時間不夠了,就要加速奔跑···

  如果這個游戲要用if或者switch條件判斷,顯得有些疲憊,如果使用狀態模式將‘跳躍’、‘開槍’、‘蹲下’和‘奔跑’作為一個個的狀態來開發,之后在遇到不同情況的時候直接使用狀態,思路將變得清晰。

代碼實現

  首先創建一個狀態對象,內部保存狀態變量,然后內部封裝好每種動作對應的狀態,最后狀態對象返回一個接口對象,它可以對內部的狀態修改或者調用。

代碼如下:

/*
* 超級瑪麗里面的狀態: 跳躍、開槍、蹲下、奔跑等
 */
const MarryState = function() {
    // 內部狀態私有變量
    let _currentState = {};
    // 動作與狀態方法映射
    const states = {
        jump() {
            // 跳躍
            console.log('jump');
        },
        move() {
            // 移動
            console.log('move');
        },
        shoot() {
            // 射擊
            console.log('shoot');
        },
        squat() {
            // 蹲下
            console.log('squat');
        }
    };
    // 動作控制類
    const Action = {
        // 改變狀態方法
        changeState() {
            // 組合動作通過傳遞多個參數實現
            let arg = arguments;
            // 重置內部狀態
            _currentState = {};
            if(arg.length) {
                // 遍歷動作
                for(let i = 0,len = arg.length; i < len; i++) {
                    // 向內部狀態中添加動作
                    _currentState[arg[i]] = true;
                }
            }
            // 返回動作控制類
            return this;
        },
        // 執行動作
        goes() {
            console.log('觸發一次動作');
            // 遍歷內部狀態保存的動作
            for(let i in _currentState) {
                // 如果改動作存在則執行
                states[i] && states[i]();
            }
            return this;
        }
    };
    // 返回接口方法 change、goes
    return {
        change: Action.changeState,
        goes: Action.goes
    }
};

 

使用方式:

// 創建一個超級瑪麗
const marry = new MarryState();
marry
    .change('jump','shoot')                 // 添加跳躍與射擊動作
    .goes()                                 // 執行動作
    .goes()                                 // 執行動作
    .change('shoot')                        // 添加射擊動作
    .goes();                                // 執行動作

 

  可以發現,狀態模式中的狀態可以連續使用。

原理和優點

  原理:將條件判斷的不同結果轉化為對象的內部狀態,作為對象內部的私有變量。

  好處:當我們需要增加、修改、調用、刪除某種狀態方法時就會很容易,也方便了我們對狀態對象中內部狀態的管理。

  用途:狀態模式是為了解決程序中臃腫的分支判斷語句問題,將每個分支轉化為一種狀態獨立出來,方便每種狀態的管理又不至於每次執行時遍歷所有分支。

  總之,狀態模式的最終目的是簡化分支判斷流程。

策略模式

使用場景

  商品的促銷活動。在聖誕節,一部分商品5折出售,一部分商品8折出售,一部分商品9折出售,等到元旦,普通用戶滿100減30,高級VIP用戶滿100減50···

  如果這種情況用if或switch來寫將是一件很費時費力的事情。

  而且對於聖誕節或者元宵節,當天的一種商品只有一種促銷策略,根本不用關心其他的促銷狀態。

代碼實現

  首先要將這些促銷算法封裝在一個策略對象內,然后對每種商品的策略調用時,直接對策略對象中的算法調用即可,為方便我們的管理與使用,我們需要返回一個調用接口對象來實現對策略算法對調用。

代碼如下:

/*
 * 價格策略對象
 */
const PriceStrategy = function () {
    // 內部算法對象
    const strategy = {
        // 100 返 30
        return30(price) {
            // parseInt可通過~~、|等運算符替換,要注意此時price要在[-2147483648,2147483647]之間
            // +price 轉換為數字類型
            return +price + parseInt(price / 100) * 30;
        },
        // 100 返 50
        return50(price) {
            return +price + parseInt(price / 100) * 50;
        },
        // 9 折
        percent90(price) {
            // JavaScript 在處理小數乘除法有bug,故運算前轉化為整數
            return price * 100 * 90 / 10000;
        },
        // 8 折
        percent80(price) {
            // JavaScript 在處理小數乘除法有bug,故運算前轉化為整數
            return price * 100 * 80 / 10000;
        },
        // 5 折
        percent50(price) {
            // JavaScript 在處理小數乘除法有bug,故運算前轉化為整數
            return price * 100 * 50 / 10000;
        }
    };
    // 策略算法調用接口
    return function (algorithm, price) {
        // 如果算法存在,則調用算法,否則返回false
        return stragtegy[algorithm] && stragtegy[algorithm](price);
    }
}();

 

使用方式:

const price = PriceStrategy('return50', '343.20');
console.log(price);

表單驗證中的策略模式

代碼如下:

/*
* 表單正則驗證側羅對象
 */
const InputStrategy = function () {
    const strategy = {
        // 是否為空
        notNull(value) {
            return /\s+/.test(value)
        },
        // 是否是一個數字
        number(value) {
            return /^[0-9]+(\.[0-9]+)?$/.test(value);
        },
        // 是否為本地電話
        phone(value) {
            // 例:010-94837837  或  0310-8899766
            return /^\d{3}\-\d{8}$|^\d{4}\-\d{7}$/.test(value);
        }
    };
    return {
        // 驗證接口 type 算法 value 表單值
        check(type, value) {
            // 去除首尾空白符
            value = value.replace(/^\s+|\s+$/g, '');
            return strategy[type] ? strategy[type](value) : '沒有該類型等檢測方法';
        },
        // 添加策略
        addStrategy(type, fn) {
            strategy[type] = fn;
        }
    }
};

  可以發現,表單驗證返回的接口中添加了一個添加策略接口。因為已有的策略即使再多,有時候也不能滿足其他工程師的需求,這樣就可以增強策略對象的拓展性。

// 拓展  可以延伸算法
InputStrategy.addStrategy('nickname', function (value) {
    return /^[a-zA-Z]\w{3,7}$/.test(value);
});

 

原理和優點

  策略模式:將定義的一組算法封裝起來,使其相互之間可以替換。

  策略模式不需要管理狀態、狀態間沒有依賴關系、策略之間可以相互替換、在策略對象內部保存的是相互獨立的一些算法。

  策略模式使得算法脫離與模塊邏輯而獨立管理,使我們可以專心研發算法,而不必受模塊邏輯所約束。

其他

  設計模式並不是很高深不可理解的一門學問,只是根據經驗把復雜的業務邏輯整理清楚,使之更容易操作化。

  根據不同的業務邏輯選擇不同的設計模式有助於簡化代碼,也有助於代碼的解耦,使得代碼更加有效和可維護。

  參考書籍:《JavaScript設計模式》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM