新生代農(nóng)民工需要懂的策略設(shè)計(jì)模式
本文轉(zhuǎn)載自微信公眾號(hào)「DYBOY」,作者DYBOY 。轉(zhuǎn)載本文請(qǐng)聯(lián)系DYBOY公眾號(hào)。
俗話說,凡事講策略。講策略的時(shí)候,我們往往會(huì)考慮每種情況的成本。策略同樣可體現(xiàn)在我們的代碼之中,合理利用策略模式重構(gòu)邏輯復(fù)雜的代碼,會(huì)使項(xiàng)目工程更易維護(hù)和擴(kuò)展。
這幾天朋友圈被“新生代農(nóng)民工”刷屏了,看到有這樣一張截圖:
新生代農(nóng)民工正名
代碼里寫了約 30 個(gè) if else 邏輯,從程序語(yǔ)義以及程序效率理論上是會(huì)有一定的影響,最主要的是可能會(huì)被其他“新生代農(nóng)民工”嘲笑。
一位經(jīng)驗(yàn)老道的民工則會(huì)用一手 switch case 或策略模式來重構(gòu)代碼,那么什么是策略模式吶?
一、定義
策略:為實(shí)現(xiàn)一定的戰(zhàn)略任務(wù),根據(jù)形勢(shì)發(fā)展而制定的行動(dòng)方針和斗爭(zhēng)方式。
策略模式:一種行為設(shè)計(jì)模式,它能讓你定義一系列算法,并將每種算法分別放入獨(dú)立的類中,以使算法的對(duì)象能夠相互替換。
在常見的前端游戲獎(jiǎng)勵(lì)激勵(lì)交互中,常常會(huì)涉及到不同分?jǐn)?shù)會(huì)展示不同的動(dòng)效,這其實(shí)就是一種條件策略。
二、優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 隔離算法的實(shí)現(xiàn)與使用
- 運(yùn)行時(shí)可切換算法
- 用組合代替繼承
- 易擴(kuò)展,符合開閉原則,無需修改上下文即可引入新策略
缺點(diǎn):
- 需要暴露所有的策略接口,用于區(qū)分策略差異
- 如果邏輯條件簡(jiǎn)單,使用策略模式會(huì)增加代碼冗余度
三、實(shí)現(xiàn)
策略模式指的是定義了一系例算法,把它們每個(gè)都封裝起來。將不變的部分和變化的部分隔離開來是設(shè)計(jì)模式中的一個(gè)重要思想,策略模式則是將算法和使用算法兩部分的實(shí)現(xiàn)拆開,降低耦合度。
基于策略模式的程序至少有兩部分組成:策略類和環(huán)境類(Context)。
策略類封裝了具體的算法,并負(fù)責(zé)具體的計(jì)算過程,可以理解為“執(zhí)行者”。
環(huán)境類(Context)接受客戶的請(qǐng)求,然后將請(qǐng)求委托給一個(gè)策略類,可以理解為“調(diào)度者”。
四、表單驗(yàn)證中的策略模式
在Web項(xiàng)目中,常見的表單有注冊(cè)、登陸、修改用戶信息等涉及到表單的功能,與此同時(shí)我們會(huì)在表單提交的時(shí)候,做一些例的前端輸入框值的條件校驗(yàn)工作。
由于輸入框中用戶的輸入是任意的,校驗(yàn)的規(guī)則相對(duì)比較復(fù)雜,如果不使用設(shè)計(jì)模式,我們的代碼中可能就會(huì)寫出較多的 if else 判斷邏輯,從可閱讀性和可維護(hù)性來說,確實(shí)不是很好。
接下來我們將從前端Web項(xiàng)目中常見的表單驗(yàn)證功能,逐步認(rèn)識(shí)策略設(shè)計(jì)模式。
4.1 初級(jí)的表單驗(yàn)證
在很久很久以前,我的表單驗(yàn)證可能是這么寫的:
- var username = $('#nuserame').val();
- var password = $('#password').val();
- if (!username) {
- alert('用戶名不能為空');
- } else if (username.length < 5) {
- alert('用戶名長(zhǎng)度需要大于等于5');
- } else if (username.length < 13) {
- alert('用戶名長(zhǎng)度需要小于13');
- } else if (!(/[a-z]+/i.test(username))) {
- alert('用戶名只能包含英文大小寫字符')
- } else {
- regeister(username);
- }
- // password的驗(yàn)證同上
寫法似乎有些不忍直視,不過能用!
4.2 基于策略模式的表單驗(yàn)證
換個(gè)思路,結(jié)合策略模式的思想,實(shí)現(xiàn)一個(gè)專用于值校驗(yàn)的 Validator 類,Validator 是一個(gè)調(diào)度著,也就是策略模式中的環(huán)境類。
然后我們?cè)隍?yàn)證目標(biāo)字段值 targetValue 的時(shí)候其用法大致如下:
- Validator.addRules(targetValue, ['isNonEmpty', 'minLength:5', 'maxLength:12']).valid();
校驗(yàn)器會(huì)返回判斷結(jié)果 result 字段,以及語(yǔ)義話的提示 msg 字段:
- return {
- result: false,
- msg: '不能為空'
- }
4.2.1 Validator
根據(jù)上述需求,Validator的實(shí)現(xiàn)如下:
- const formatResult = (isPass: boolean = false, errMsg: string = "") => {
- return {
- result: isPass,
- msg: errMsg,
- };
- };
- const ValidStrategies = {
- isNonEmpty: function (val: string = "") {
- if (!val) return formatResult(false, "內(nèi)容不能為空");
- },
- minLength: function (val: string = "", length: string | number = 0) {
- console.log(val, length);
- if (typeof length === "string") length = parseInt(length);
- if (val.length < length)
- return formatResult(false, `內(nèi)容長(zhǎng)度不能小于${length}`);
- },
- maxLength: function (val: string = "", length: string | number = 0) {
- if (typeof length === "string") length = parseInt(length);
- if (val.length > length)
- return formatResult(false, `內(nèi)容長(zhǎng)度不能大于${length}`);
- },
- default: function () {
- return formatResult(true);
- },
- };
- /**
- * 驗(yàn)證器
- * 策略模式 —— 環(huán)境類,負(fù)責(zé)調(diào)度算法
- */
- class Validator {
- // 存儲(chǔ)規(guī)則
- private _ruleExecuters: Array<any>;
- constructor() {
- this._ruleExecuters = [];
- }
- /**
- * addRules
- * 添加校驗(yàn)規(guī)則
- */
- public addRules(value: string = "", rules: Array<string>) {
- this._ruleExecuters = [];
- rules.forEach((rule) => {
- const args = rule.split(":");
- const functionName = args.shift() || "default";
- // 忽略下這里的斷言類型👀
- const ruleFunc = ValidStrategies[
- functionName as "isNonEmpty" | "minLength" | "maxLength" | "default"
- ].bind(this, value);
- this._ruleExecuters.push({
- func: ruleFunc,
- args,
- });
- });
- return this;
- }
- /**
- * valid
- */
- public valid() {
- for (let i = 0; i < this._ruleExecuters.length; i++) {
- const res = this._ruleExecuters[i].func.apply(
- this,
- this._ruleExecuters[i].args
- );
- if (res && !res.result) {
- return res;
- }
- }
- return formatResult(true);
- }
- }
- export default new Validator();
- const res = Validator.addRules("123", [
- "isNonEmpty",
- "minLength:5",
- "maxLength:12",
- ]).valid();
- console.log("res:", res);
執(zhí)行結(jié)果
這樣在驗(yàn)證表單值的時(shí)候,我們就可以直接調(diào)用 Validator 驗(yàn)證值的合法性。
與此同時(shí),還可以通過擴(kuò)展策略類(對(duì)象)ValidStrategies 中的驗(yàn)證算法來擴(kuò)展校驗(yàn)器的能力。
五、表驅(qū)動(dòng)法
策略模式節(jié)省邏輯判斷的特性讓我聯(lián)想到了之前看過的一個(gè)概念“表驅(qū)動(dòng)法”,或者叫“查表法”,這里引用下百度百科的解釋:
表驅(qū)動(dòng)方法出于特定的目的來使用表,程序員們經(jīng)常談到“表驅(qū)動(dòng)”方法,但是課本中卻從未提到過什么是"表驅(qū)動(dòng)"方法。表驅(qū)動(dòng)方法是一種使你可以在表中查找信息,而不必用很多的邏輯語(yǔ)句(if 或 Case)來把它們找出來的方法。事實(shí)上,任何信息都可以通過表來挑選。在簡(jiǎn)單的情況下,邏輯語(yǔ)句往往更簡(jiǎn)單而且更直接。但隨著邏輯鏈的復(fù)雜,表就變得越來越富有吸引力了。
舉個(gè)例子,假設(shè)我們想要獲取當(dāng)前是星期幾,代碼可能是這樣的:
- function getDay() {
- const day = (new Date()).getDay();
- switch (day) {
- case 0:
- return '星期日';
- case 1:
- return '星期一';
- // ...
- case 6:
- return '星期六';
- default:
- return '';
- }
- }
如果是初次編程的同學(xué)還可能會(huì)有 if else 條件語(yǔ)句來判斷返回值,代碼就會(huì)顯得比較冗余。
借助表驅(qū)動(dòng)發(fā)法的思想,這里我們是可以有優(yōu)化空間的,表驅(qū)動(dòng)發(fā)法的寫法如下:
- const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
- function getDay2() {
- return days[(new Date()).getDay()];
- }
當(dāng)然上述只是一個(gè)非常簡(jiǎn)單的??,大家在編碼過程中只需要主要點(diǎn),如有涉及類似場(chǎng)景,請(qǐng)用這種方式去編碼,體驗(yàn)更愉悅!
六、總結(jié)
策略設(shè)計(jì)模式讓各種算法的代碼、內(nèi)部數(shù)據(jù)和依賴關(guān)系與其他代碼隔離開來。不同客戶端可通過一個(gè)簡(jiǎn)單接口執(zhí)行算法,并能在運(yùn)行時(shí)進(jìn)行切換。
當(dāng)然在設(shè)計(jì)實(shí)現(xiàn)程序功能的時(shí)候,如果需要使用策略設(shè)計(jì)模式,也更需要我們的工程師有一個(gè)功能全局把控的能力,才能更好將依賴關(guān)系拆分,抽象化,以此才能凸顯“新生代”民工的不同!