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