JavaScript設(shè)計模式之策略模式(Strategy)

在程序設(shè)計中,我們也常常遇到這樣的情況芯肤,要實現(xiàn)某一個功能有多種方案可以選擇巷折。比如一個壓縮文件的程序,既可以選擇 zip 算法崖咨,也可以選擇 gzip 算法锻拘。
這些算法靈活多樣,而且可以隨意互相替換击蹲。這種解決方案就是將要介紹的策略模式署拟。

定義

策略模式的定義是:定義一系列的算法,把它們一個個封裝起來歌豺,并且使它們可以相互替換推穷。

從定義上看,策略模式就是用來封裝算法的类咧。但如果把策略模式僅僅用來封裝算法馒铃,未免有一點大材小用谴咸。在實際開發(fā)中,我們通常會把算法的含義擴散開來骗露,使策略模式也可以用來封裝一系列的“業(yè)務(wù)規(guī)則”岭佳。只要這些業(yè)務(wù)規(guī)則指向的目標一致,并且可以被替換使用萧锉,我們就可以用策略模式來封裝它們珊随。

使用實例

表單驗證

在一個 Web 項目中,注冊柿隙、登錄叶洞、修改用戶信息等功能的實現(xiàn)都離不開提交表單。

在將用戶輸入的數(shù)據(jù)交給后臺之前禀崖,常常要做一些客戶端力所能及的校驗工作衩辟,比如注冊的時候需要校驗是否填寫了用戶名,密碼的長度是否符合規(guī)定等等波附。這樣可以避免因為提交不合法數(shù)據(jù)而帶來的不必要網(wǎng)絡(luò)開銷艺晴。

假設(shè)我們正在編寫一個注冊的頁面,在點擊注冊按鈕之前掸屡,有如下幾條校驗邏輯封寞。

  • 用戶名不能為空。
  • 密碼長度不能少于 6 位
  • 手機號碼必須符合格式仅财。

在理解策略模式之前狈究,通常我們遇到類似多條件的業(yè)務(wù),按照swith語句來判斷盏求,但是這就帶來幾個問題抖锥,首先如果增加需求的話,我們還要再次修改這段代碼以增加邏輯碎罚,而且在進行單元測試的時候也會越來越復(fù)雜磅废,代碼如下:

 var validator = {
     validate: function (value, type) {
         switch (type) {
             case 'isNonEmpty ':
                     return true; // NonEmpty 驗證結(jié)果
             case 'minLength ':
                     return true; // minLength 驗證結(jié)果
                     break;
             case 'isMobile ':
                     return true; // isMobile 驗證結(jié)果
             default:
                     return true;
         }
     }
 };
 // 測試
 alert(validator.validate("123", "isNonEmpty"));

用策略模式重構(gòu)表單校驗

用策略模式來重構(gòu)表單校驗的代碼,我們可以將相同的工作代碼單獨封裝成不同的類魂莫,然后通過統(tǒng)一的策略處理類來處理还蹲,OK,我們先來定義策略處理類耙考,代碼如下:

var validator = {

    // 所有可以的驗證規(guī)則處理類存放的地方,后面會單獨定義
    types: {},

    // 驗證類型所對應(yīng)的錯誤消息
    messages: [],

    // 當然需要使用的驗證類型
    config: {},

    // 暴露的公開驗證方法
    // 傳入的參數(shù)是 key => value對
    validate: function (data) {

        var i, msg, type, checker, result_ok;

        // 清空所有的錯誤信息
        this.messages = [];

        for (i in data) {
            if (data.hasOwnProperty(i)) {

                type = this.config[i];  // 根據(jù)key查詢是否有存在的驗證規(guī)則
                checker = this.types[type]; // 獲取驗證規(guī)則的驗證類

                if (!type) {
                    continue; // 如果驗證規(guī)則不存在潭兽,則不處理
                }
                if (!checker) { // 如果驗證規(guī)則類不存在倦始,拋出異常
                    throw {
                        name: "ValidationError",
                        message: "No handler to validate type " + type
                    };
                }

                result_ok = checker.validate(data[i]); // 使用查到到的單個驗證類進行驗證
                if (!result_ok) {
                    msg = "Invalid value for *" + i + "*, " + checker.instructions;
                    this.messages.push(msg);
                }
            }
        }
        return this.hasErrors();
    },

    // helper
    hasErrors: function () {
        return this.messages.length !== 0;
    }
};

然后剩下的工作,就是定義types里存放的各種驗證類了:

// 驗證給定的值是否不為空
validator.types.isNonEmpty = {
    validate: function (value) {
        return value !== "";
    },
    instructions: "傳入的值不能為空"
};

// 驗證給定的值是否是數(shù)字
validator.types.isNumber = {
    validate: function (value) {
        return !isNaN(value);
    },
    instructions: "傳入的值只能是合法的數(shù)字山卦,例如:1, 3.14 or 2010"
};

// 驗證給定的值是否只是字母或數(shù)字
validator.types.isAlphaNum = {
    validate: function (value) {
        return !/[^a-z0-9]/i.test(value);
    },
    instructions: "傳入的值只能保護字母和數(shù)字鞋邑,不能包含特殊字符"
};

使用的時候诵次,我們首先要定義需要驗證的數(shù)據(jù)集合,然后還需要定義每種數(shù)據(jù)需要驗證的規(guī)則類型枚碗,代碼如下:

var data = {
    first_name: "Tom",
    last_name: "Xu",
    age: "unknown",
    username: "TomXu"
};

validator.config = {
    first_name: 'isNonEmpty',
    age: 'isNumber',
    username: 'isAlphaNum'
};

最后逾一,獲取驗證結(jié)果的代碼就簡單了:

validator.validate(data);

if (validator.hasErrors()) {
    console.log(validator.messages.join("\n"));
}

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

策略模式是一種常用且有效的設(shè)計模式,我們可以總結(jié)出策略模式的一些優(yōu)點:

  1. 策略模式利用組合肮雨、委托和多態(tài)等技術(shù)和思想遵堵,可以有效地避免多重條件選擇語句。
  2. 策略模式提供了對開放—封閉原則的完美支持怨规,將算法封裝在獨立的 strategy 中陌宿,使得它們易于切換,易于理解波丰,易于擴展壳坪。
  3. 策略模式中的算法也可以復(fù)用在系統(tǒng)的其他地方,從而避免許多重復(fù)的復(fù)制粘貼工作掰烟。
  4. 在策略模式中利用組合和委托來讓 Context 擁有執(zhí)行算法的能力爽蝴,這也是繼承的一種更輕便的替代方案。

當然纫骑,策略模式也有一些缺點霜瘪,但這些缺點并不嚴重。

首先惧磺,使用策略模式會在程序中增加許多策略類或者策略對象颖对,但實際上這比把它們負責(zé)的 邏輯堆砌在 Context 中要好。

其次磨隘,要使用策略模式缤底,必須了解所有的 strategy,必須了解各個 strategy 之間的不同點番捂, 這樣才能選擇一個合適的 strategy个唧。比如,我們要選擇一種合適的旅游出行路線设预,必須先了解選 擇飛機徙歼、火車、自行車等方案的細節(jié)鳖枕。此時 strategy 要向客戶暴露它的所有實現(xiàn)魄梯,這是違反最少 知識原則的。

總結(jié)

策略模式定義了一系列算法宾符,從概念上來說酿秸,所有的這些算法都是做相同的事情,只是實現(xiàn)不同魏烫,他可以以相同的方式調(diào)用所有的方法辣苏,減少了各種算法類與使用算法類之間的耦合肝箱。

從另外一個層面上來說,單獨定義算法類稀蟋,也方便了單元測試煌张,因為可以通過自己的算法進行單獨測試。

實踐中退客,不僅可以封裝算法骏融,也可以用來封裝幾乎任何類型的規(guī)則,是要在分析過程中需要在不同時間應(yīng)用不同的業(yè)務(wù)規(guī)則井辜,就可以考慮是要策略模式來處理各種變化绎谦。


參考引用資料
湯姆大叔的博客——深入理解JavaScript系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粥脚,隨后出現(xiàn)的幾起案子窃肠,更是在濱河造成了極大的恐慌,老刑警劉巖刷允,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冤留,死亡現(xiàn)場離奇詭異,居然都是意外死亡树灶,警方通過查閱死者的電腦和手機纤怒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來天通,“玉大人泊窘,你說我怎么就攤上這事∠窈” “怎么了烘豹?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長诺祸。 經(jīng)常有香客問我携悯,道長,這世上最難降的妖魔是什么筷笨? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任憔鬼,我火速辦了婚禮,結(jié)果婚禮上胃夏,老公的妹妹穿的比我還像新娘轴或。我一直安慰自己,他們只是感情好构订,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布侮叮。 她就那樣靜靜地躺著,像睡著了一般悼瘾。 火紅的嫁衣襯著肌膚如雪囊榜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天亥宿,我揣著相機與錄音卸勺,去河邊找鬼。 笑死烫扼,一個胖子當著我的面吹牛曙求,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播映企,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼悟狱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堰氓?” 一聲冷哼從身側(cè)響起挤渐,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎双絮,沒想到半個月后浴麻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡囤攀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年软免,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焚挠。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡膏萧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝌衔,到底是詐尸還是另有隱情榛泛,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布胚委,位于F島的核電站挟鸠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亩冬。R本人自食惡果不足惜艘希,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望硅急。 院中可真熱鬧覆享,春花似錦、人聲如沸营袜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荚板。三九已至凤壁,卻和暖如春吩屹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拧抖。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工煤搜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唧席。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓擦盾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淌哟。 傳聞我的和親對象是個殘疾皇子迹卢,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情徒仓,實現(xiàn)同樣的效果;這時候需要使用工廠模式腐碱。簡單...
    舟漁行舟閱讀 7,718評論 2 17
  • 1 場景問題# 1.1 報價管理## 向客戶報價,對于銷售部門的人來講蓬衡,這是一個非常重大喻杈、非常復(fù)雜的問題,對不同的...
    七寸知架構(gòu)閱讀 5,051評論 9 62
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評論 25 707
  • 1 場景問題 1.1 報價管理 向客戶報價狰晚,對于銷售部門的人來講筒饰,這是一個非常重大、非常復(fù)雜的問題壁晒,對不同的客戶要...
    4e70992f13e7閱讀 3,071評論 2 16
  • 今天文章的主角叫武大郎瓷们,家住山東清河縣,因長的矮小且相貌丑陋秒咐,人送外號“三寸丁谷樹皮”谬晕,這位仁兄跟他媳婦潘女士,可...
    強哥趣談歷史閱讀 1,116評論 0 0