面試官:策略模式有使用過嗎?我:沒有......

面試官:策略模式有使用過嗎着饥?我:沒有......

何為策略模式犀农?

  • 比如在業(yè)務邏輯或程序設計中比如要實現某個功能,有多種方案可供我們選擇宰掉。比如要壓縮一個文件呵哨,我們既可以選擇 ZIP 算法谤逼,也可以選擇 GZIP 算法。
  • 這些算法靈活多樣仇穗,可隨意切換流部,而這種解決方案就是我們所要學習的策略模式。

定義或概念

  • 策略模式:定義一系列的算法纹坐,將他們一個個封裝枝冀,并使他們可相互替換。

策略模式的最佳實踐

例子1:獎金計算

  • 題目:在很多公司的年終獎都是按照員工的工資基數和年底績效情況來發(fā)放的耘子,例如果漾,績效為 S 的人年終獎有 4 倍工資,A 的人年終獎有 3 倍谷誓,B 的人年終獎有 2 倍绒障。要求我們寫出一個程序來更快的計算員工的年終獎。(編寫一個名為 calcBonus 方法來計算每個員工的獎金數額)
  • 可能有些人一上來直接就在一個方法中進行很多 if...else 或 switch...case 判斷, 然后通過這個方法進行計算捍歪。我們可以來試著寫一下:
/**
 *
 * @param {*} level 績效等級
 * @param {*} salary 工資基數
 * @returns 年終獎金額
 */
var calcBonus = function (level, salary) {
    if (level === "S") {
        return salary * 4;
    } else if (level === "A") {
        return salary * 3;
    } else if (level === "B") {
        return salary * 2;
    }
};

calcBonus('A', 20000); // 60000
calcBonus('B', 8000); // 16000
  • 我想在我們每個人初學代碼時肯定都寫出過這樣的代碼户辱。其實這段代碼有顯而易見的缺點:
    1. calcBonus 函數邏輯太多
    2. calcBonus 函數缺乏彈性,比如如果我們需要增加一個等級 C糙臼,那就必須要去修改 calcBonus 函數庐镐。這就違反了開放-封閉原則。
    3. 復用性差变逃。如果后續(xù)還要重用這個程序去計算獎金必逆,我們只有去 C,V。
  • 此時揽乱,可能會想對 calcBonus 函數進行封裝名眉,如我們使用組合函數的形式,如下:
var totalS = function (salary) {
    return salary * 4;
};
var totalA = function (salary) {
    return salary * 3;
};
var totalB = function (salary) {
    return salary * 2;
};

var calcBonus = function (level, salary) {
    if (level === "S") {
        return totalS(salary);
    } else if (level === "A") {
        return totalA(salary);
    } else if (level === "B") {
        return totalB(salary);
    }
};

calcBonus('A', 20000); // 60000
calcBonus('B', 8000); // 16000
  • 這樣凰棉,我們將程序進行了進一步改善损拢,但改善微乎其微,依舊沒有解決最重要的問題渊啰,calcBonus 函數還是有可能會很龐大探橱,并且也沒有彈性。
  • 那我們再將它進行一次改造绘证,使用策略模式:將其定義為一系列的算法隧膏,將他們每一個封裝起來,將不變的部分和變化的部分隔開嚷那。
  • 在這段程序中胞枕,算法的使用方式是不變的,都是根據某個算法獲取最后的獎金金額魏宽。而在每個算法的內部實現卻是不同的腐泻,每一個等級對應著不同的計算規(guī)則决乎。
  • 在策略模式程序中:最少由兩部分組成,一部分是一組策略類派桩,在策略類中封裝了具體的算法构诚,并負責具體的計算過程。一部分是環(huán)境類 context铆惑,接受用戶的請求范嘱,并將請求委托給某一個策略類。
  • 如下:
var strategies = {
    S: function (salary) {
        return salary * 4;
    },
    A: function (salary) {
        return salary * 3;
    },
    B: function (salary) {
        return salary * 2;
    },
};

var calcBonus = function (level, salary) {
    return strategies[level](salary);
}

calcBonus('A', 20000); // 60000
calcBonus('B', 8000); // 16000
  • 其實员魏,策略模式的實現并不復雜丑蛤,關鍵是如何從策略模式的實現背后,找到封裝變化撕阎,委托和多態(tài)性這些思想的價值受裹。

例子2:表單驗證

  • 題目:在 Web 開發(fā)中,表單校驗是一個常見的話題虏束,要求使用策略模式來完成表單驗證棉饶。
  • 比如:
    1. 用戶名不能為空
    2. 密碼長度不能少于 6 位
    3. 手機號碼必須符合正確格式
  • 讓我們來實現一下吧:
function submit() {
    let { username, password, tel } = infoForm;
    if (username === "") {
        Toast("用戶名不能為空");
        return false;
    }
    if (password.length < 6) {
        Toast("密碼不能少于 6 位");
        return false;
    }
    if (!/(^1[3|5|8][0-9]{9}$)/.test(tel)) {
        Toast("手機號碼格式不正確");
        return false;
    }

    // .....
}
  • 這是我們常見的實現方式,它的缺點跟計算獎金一例類似:
    1. submit 函數龐大魄眉,包含了很多 if...else 語句
    2. submit 函數缺乏彈性砰盐,如果對其新加一些新的校驗規(guī)則,如果我們把密碼長度從 6 改到 8.那我們就必須要改動 submit 函數坑律,否則無法實現該校驗。這也是違反開放-封閉原則囊骤。
    3. 復用差晃择,如果說我們程序中還有另一個表達需要驗證,也是進行類似的校驗也物,那我們可能會進行 C, V 操作宫屠。
  • 使用策略模式來進行重構
let infoForm = {
    username: "我是某某某",
    password: 'zxcvbnm',
    tel: 16826384655,
};

var strategies = {
    isEmpty: function (val, msg) {
        if (!val) return msg;
    },
    minLength: function (val, length, msg) {
        if (val.length < length) return msg;
    },
    isTel: function (val, msg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(val)) return msg;
    },
};

var validFn = function () {
    var validator = new Validator();

    let { username, password, tel } = infoForm;

    validator.add(username, "isEmpty", "用戶名不能為空");
    validator.add(password, "minLength:6", "密碼不能少于 6 位");
    validator.add(tel, "isTel", "手機號碼格式不正確");

    var msg = validator.start();
    return msg;
};

class Validator {
    constructor() {
        this.cache = [];
    }
    add(attr, rule, msg) {
        var ruleArr = rule.split(":");
        this.cache.push(function () {
            var strategy = ruleArr.shift();
            ruleArr.unshift(attr);
            ruleArr.push(msg);
            return strategies[strategy].apply(attr, ruleArr);
        });
    }

    start() {
        for (let i = 0; i < this.cache.length; i++) {
            var msg = this.cache[i]();
            if (msg) return msg;
        }
    }
}

function submit() {
    let msg = validFn();
    if (msg) {
        Toast(msg);
        return false;
    }
    console.log('verify success');

    // .....
}

submit();
  • 使用策略模式重構后,我們后續(xù)僅需配置的方式來完成滑蚯。
  • 擴展題目:那如果想給用戶名還想再添加一個規(guī)則浪蹂,那如何完成呢?
  • 添加規(guī)則方式如下:
validator.add(username, [
    {
        strategy: "isEmpty",
        msg: "用戶名不能為空"
    },
    {
        strategy: 'minLength:6',
        msg: '密碼不能少于 6 位'
    }
]);
  • 實現:
let infoForm = {
    username: "阿斯頓發(fā)生的",
    password: "ss1sdf",
    tel: 15829485647,
};

var strategies = {
    isEmpty: function (val, msg) {
        if (!val) return msg;
    },
    minLength: function (val, length, msg) {
        if (val.length < length) return msg;
    },
    isTel: function (val, msg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(val)) return msg;
    },
};

var validFn = function () {
    var validator = new Validator();

    let { username, password, tel } = infoForm;

    validator.add(username, [
        {
            strategy: "isEmpty",
            msg: "用戶名不能為空",
        },
        {
            strategy: "minLength:6",
            msg: "密碼不能少于 6 位",
        },
    ]);
    validator.add(password, [
        {
            strategy: "minLength:6",
            msg: "密碼不能少于 6 位",
        },
    ]);
    validator.add(tel, [
        {
            strategy: "isTel",
            msg: "手機號碼格式不正確",
        },
    ]);

    var msg = validator.start();
    return msg;
};

class Validator {
    constructor() {
        this.cache = [];
    }
    add(attr, rules) {
        for (let i = 0; i < rules.length; i++) {
            var rule = rules[i];
            var ruleArr = rule.strategy.split(":");
            var msg = rule.msg;
            var cacheItem = this.createCacheItem(ruleArr, attr, msg);
            this.cache.push(cacheItem);
        }
    }

    start() {
        for (let i = 0; i < this.cache.length; i++) {
            var msg = this.cache[i]();
            if (msg) return msg;
        }
    }

    createCacheItem(ruleArr, attr, msg) {
        return function () {
            var strategy = ruleArr.shift();
            ruleArr.unshift(attr);
            ruleArr.push(msg);
            return strategies[strategy].apply(attr, ruleArr);
        };
    }
}

function submit() {
    let msg = validFn();
    if (msg) {
        Toast(msg);
        return false;
    }
    console.log("verify success");

    // .....
}

submit();

策略模式的優(yōu)缺點

  • 優(yōu)點:
    1. 利用組合告材,委托坤次,多態(tài)等技術有效避免了多重條件語句
    2. 提供了對開封-封閉原則的完美支持
    3. 復用性較強,避免許多重復的 C,V 工作
  • 缺點:
    1. 客戶端比如了解所有的策略類斥赋,并選擇合適的策略類缰猴。

策略模式的角色

  1. Context(環(huán)境類):持有一個 Strategy 類的引用,用一個 ConcreteStrategy 對象來配置
  2. Strategy(環(huán)境策略類):定義了所有支持的算法的公共接口疤剑,通常是以一個接口或抽象來實現滑绒。Context 使用這個接口來調用其 ConcreteStrategy 定義的算法闷堡。
  3. ConcreteStrategy(具體策略類):以 Strategy 接口實現某種算法
  • 比如以上的例子算法:


    e9a698551f352.png

策略模式的應用場景

  1. 想使用對象中各種不同算法變體來在運行時切換算法時
  2. 擁有很多在執(zhí)行某些行為時有著不同的規(guī)則時

Tip: 文章部分內容參考于曾探大佬的《JavaScript 設計模式與開發(fā)實踐》。文章僅做個人學習總結

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末疑故,一起剝皮案震驚了整個濱河市杠览,隨后出現的幾起案子,更是在濱河造成了極大的恐慌纵势,老刑警劉巖倦零,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異吨悍,居然都是意外死亡扫茅,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門育瓜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葫隙,“玉大人,你說我怎么就攤上這事躏仇×到牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵焰手,是天一觀的道長糟描。 經常有香客問我,道長书妻,這世上最難降的妖魔是什么船响? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮躲履,結果婚禮上见间,老公的妹妹穿的比我還像新娘。我一直安慰自己工猜,他們只是感情好米诉,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著篷帅,像睡著了一般史侣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上魏身,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天惊橱,我揣著相機與錄音,去河邊找鬼叠骑。 笑死李皇,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播掉房,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼茧跋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卓囚?” 一聲冷哼從身側響起瘾杭,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哪亿,沒想到半個月后粥烁,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蝇棉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年讨阻,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篡殷。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡钝吮,死狀恐怖,靈堂內的尸體忽然破棺而出板辽,到底是詐尸還是另有隱情奇瘦,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布劲弦,位于F島的核電站耳标,受9級特大地震影響,放射性物質發(fā)生泄漏邑跪。R本人自食惡果不足惜次坡,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呀袱。 院中可真熱鬧贸毕,春花似錦、人聲如沸夜赵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寇僧。三九已至,卻和暖如春沸版,著一層夾襖步出監(jiān)牢的瞬間嘁傀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工视粮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留细办,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像笑撞,于是被迫代替她去往敵國和親岛啸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容