JS設計模式-策略模式

mmtdxslb 7年前發布 | 15K 次閱讀 策略模式 JavaScript開發 JavaScript

前情提要

面向過程:將項目分解成很多步驟,為每個步驟編寫代碼。可維護性差。

面向對象:將項目分解成一個一個方法,然后根據每個步驟的需要調用方法。可維護性好。

面向對象的SOLID原則

  1. 單一職責:一個類有且只有一個職責

  2. 開放封閉:軟件實體(類,模塊,函數等)應該對擴展開放,對修改關閉

  3. 里氏替換:子類型必須能夠替換它們的基類

  4. 接口分離:接口只應該包括必要的方法而不是所有的

  5. 依賴倒置:高層次的模塊不應該依賴于低層次的模塊,而是都應該依賴于抽象

設計模式是啥?

設計模式是一套經驗,是對于某一類特定問題的簡潔而優雅的解決方案

  1. 代碼會具有更好的可維護性

  2. 學習設計模式,幫助我們更好的理解與運用SOLID

設計模式-策略模式

應用場景:當需要根據不同情景從多種算法中選擇一種執行時

重點思想:將變與不變分離

具體實現:將 不變的 算法封裝在 策略類 中, 策略類 只負責算法,傳入參數傳出參數; 變化的 是客戶端的請求,放在 情景類 中, 情景類 負責接收客戶端的請求并委托給 策略類

舉例1:會員折扣價格計算

需求:對 A 類型會員提供 20% 的促銷折扣,對 B 類型會員提供 10% 的促銷折扣,對 C 類型會員沒有折扣

首先我們定義會員折扣

// 定義不同會員的折扣
const discountA = 0.8,
    discountB = 0.9,
    discountC = 1;

最初的實現↓

// 計算折扣價格函數
function calculatePrice(type, totalPrice) {
    if(type === 'A') {
        return totalPrice*discountA;
    }
    if(type === 'B') {
        return totalPrice*discountB;
    }
    if(type === 'C') {
        return totalPrice*discountC;
    }
}
// 調用函數
console.log(calculatePrice('A',100));

分析:算法是不變的,變的是會員類型和總價

運用策略模式↓

// 定義策略對象
const strategies = {
    A: function(totalPrice) {
        return totalPrice*discountA;
    },
    B: function(totalPrice) {
        return totalPrice*discountB;
    },
    C: function(totalPrice) {
        return totalPrice*discountC;
    },
}
// 定義情景類
function calculatePrice(type, totalPrice) {
    return strategies[type](totalPrice);
}
// 調用函數計算折扣價
console.log(calculatePrice('A',100));

策略對象還可以進一步抽象。

// 定義策略對象
const strategies = {
    A: discountA,
    B: discountB,
    C: discountC,
}
// 定義情景類
function calculatePrice(type, totalPrice) {
    return strategies[type]*(totalPrice);
}
// 調用函數計算折扣價
console.log(calculatePrice('A',100));

舉例2:注冊頁面表單驗證

需求: 用戶名 不可為空, 密碼 不能小于6位, 手機號碼 格式正確

// 偽一組數據
const registerForm = {
    userName: 'liuxiaocui',
    password: 'lxc123456',
    phone: '18628337983',
}

最初的實現↓

// 校驗函數
function validate(data) {
    if(data.userName === '') {
        console.log('用戶名不能為空');
        return;
    }
    if (data.password.length < 6) {
        console.log('密碼長度不能小于6位');
        return;
    }
    if (!/(^1[3|5|8][0-9]{9}$)/.test(data.phone)) {
        console.log('手機號碼格式不正確');
        return;
    }
    return true;
}
// 提交表單函數
function onSubmit(data) {
    if(validate(data)) {
        console.log('通過校驗');
        // 別的操作blabla
    }
}
// 調用提交表單函數
onSubmit(registerForm);

分析:校驗算法是不變的,變的是數據和校驗要求

運用策略模式↓

首先定義策略對象,注意每個函數的傳參。所有參數應該外部傳入。

// 定義策略對象
const strategies = {
    // 校驗為空
    isNonEmpty: function (value, errorMsg) {
        if (value === '') {
            return errorMsg;
        }
    },
    // 校驗最小長度
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    },
    // 校驗手機號碼格式
    isMobile: function (value, errorMsg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }                
    }
}

回顧下需求。我們希望能方便得對數據進行校驗,也很可能一個數據需要多條校驗規則。

先來看下校驗函數是怎么寫的。

// 校驗函數
function validate(data) {
    const validator = new Validator();
    validator.add(data.userName, [{
        strategy: 'isNonEmpty',
        errorMsg: '用戶名不能為空'
    }, {
        strategy: 'minLength:10',
        errorMsg: '用戶名長度不能小于10位'
    }]);
    validator.add(data.password, [{
        strategy: 'minLength:6',
        errorMsg: '密碼長度不能小于6位'
    }]);
    validator.add(data.phone, [{
        strategy: 'isMobile',
        errorMsg: '手機號碼格式不正確'
    }]);
    return validator.start();
}
// 提交表單函數
function onSubmit(data) {
    const errorMsg = validate(data);
    if(errorMsg) {
        console.log(errorMsg);
    } else {
        console.log('通過校驗');
        // 別的操作blabla
    }
}
// 調用函數
onSubmit(registerForm);

下面是情景類。

// 定義情景類
var Validator = function() {
    // 存下需要進行的校驗
    this.cache = [];
}
// 增加需要進行的校驗
Validator.prototype.add = function(data, rules) {
    rules.forEach( item => {
        // strategyAry的第一個元素為strategy名字,第二個值(如果有)為限制值
        const strategyAry = item.strategy.split(':');
        const errorMsg = item.errorMsg;
        this.cache.push(function() {
            const strategy = strategyAry.shift();
            // unshift() 方法可向數組的開頭添加一個或更多元素,并返回新的長度。
            // 把要校驗的數據放在數組第一位
            strategyAry.unshift(data);
            // 把錯誤信息放在數組最后一位
            strategyAry.push(errorMsg);
            // return strategies[strategy](...strategyAry);
            return strategies[strategy].apply(null,strategyAry);
        })
    })
}
// 開始校驗
Validator.prototype.start = function() {
    // 挨個運行this.cache中的校驗函數
    for(let validatorFunc of this.cache) {
        const errorMsg = validatorFunc();
        if(errorMsg) {
            return errorMsg;
        }
    }
}

小結

策略模式優點:將不變的算法單獨封裝在 策略類 中,通過 情景類 來接收請求并委托給 策略類 ,代碼結構清晰,使用方便

策略模式缺點:使用之前需要知道所有的策略類函數

 

來自:https://segmentfault.com/a/1190000008773507

 

 本文由用戶 mmtdxslb 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!