JavaScript版本的策略模式,javascript版本原則

來源:互聯網
上載者:User

JavaScript版本的策略模式,javascript版本原則

俗話說,條條大路通羅馬。在美劇《越獄》中,主角Michael Scofield就設計了兩條越獄的道路。這兩條道路都可以到達靠近監獄外牆的醫務室。

同樣,在現實中,很多時候也有多種途徑到達同一個目的地。比如我們要去某個地方旅遊,可以根據具體的實際情況來選擇出行的線路。

  • 如果沒有時間但是不在乎錢,可以選擇坐飛機。
  • 如果沒有錢,可以選擇坐大巴或者火車。
  • 如果再窮一點,可以選擇騎單車。


在程式設計中,我們也常常遇到類似的情況,要實現某一個功能有多種方案可以選擇。比如一個壓縮檔的程式,既可以選擇zip演算法,也可以選擇gzip演算法。

這些演算法靈活多樣,而且可以隨意互相替換。這種解決方案就是本章將要介紹的策略模式。

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

策略模式有著廣泛的應用。本節我們就以年終獎的計算為例進行介紹。

很多公司的年終獎是根據員工的工資基數和年底績效情況來發放的。例如,績效為S的人年終獎有4倍工資,績效為A的人年終獎有3倍工資,而績效為B的人年終獎是2倍工資。假設財務部要求我們提供一段代碼,來方便他們計算員工的年終獎。

1. 最初的代碼實現

我們可以編寫一個名為calculateBonus的函數來計算每個人的獎金數額。很顯然,calculateBonus函數要正確工作,就需要接收兩個參數:員工的工資數額和他的績效考核等級。代碼如下:

var calculateBonus = function( performanceLevel, salary ){    if ( performanceLevel === 'S' ){        return salary * 4;    }    if ( performanceLevel === 'A' ){        return salary * 3;    }    if ( performanceLevel === 'B' ){        return salary * 2;    }};calculateBonus( 'B', 20000  );        // 輸出:40000calculateBonus( 'S', 6000 );        // 輸出:24000

可以發現,這段代碼十分簡單,但是存在著顯而易見的缺點。

  • calculateBonus函數比較龐大,包含了很多if-else語句,這些語句需要覆蓋所有的邏輯分支。

  • calculateBonus函數缺乏彈性,如果增加了一種新的績效等級C,或者想把績效S的獎金係數改為5,那我們必須深入calculateBonus函數的內部實現,這是違反開放-封閉原則的。

  • 演算法的複用性差,如果在程式的其他地方需要重用這些計算獎金的演算法呢?我們的選擇只有複製和粘貼。

因此,我們需要重構這段代碼。

2. 使用組合函數重構代碼

一般最容易想到的辦法就是使用組合函數來重構它,我們把各種演算法封裝到一個個的小函數裡面,這些小函數有著良好的命名,可以一目瞭然地知道它對應著哪種演算法,它們也可以被複用在程式的其他地方。代碼如下:

var performanceS = function( salary ){    return salary * 4;};var performanceA = function( salary ){    return salary * 3;};var performanceB = function( salary ){    return salary * 2;};var calculateBonus = function( performanceLevel, salary ){    if ( performanceLevel === 'S' ){        return performanceS( salary );    }    if ( performanceLevel === 'A' ){        return performanceA( salary );    }    if ( performanceLevel === 'B' ){        return performanceB( salary );    }};calculateBonus(  'A' , 10000 );    // 輸出:30000

目前,我們的程式得到了一定的改善,但這種改善非常有限,我們依然沒有解決最重要的問題:calculateBonus函數有可能越來越龐大,而且在系統變化的時候缺乏彈性。

3. 使用原則模式重構代碼

經過思考,我們想到了更好的辦法——使用原則模式來重構代碼。策略模式指的是定義一系列的演算法,把它們一個個封裝起來。將不變的部分和變化的部分隔開是每個設計模式的主題,策略模式也不例外,策略模式的目的就是將演算法的使用與演算法的實現分離開來。

在這個例子裡,演算法的使用方式是不變的,都是根據某個演算法取得計算後的獎金數額。而演算法的實現是各異和變化的,每種績效對應著不同的計算規則。

一個基於策略模式的程式至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體的演算法,並負責具體的計算過程。 第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委託給某一個策略類。要做到這點,說明Context中要維持對某個策略對象的引用。

現在用策略模式來重構上面的代碼。第一個版本是模仿傳統物件導向語言中的實現。我們先把每種績效的計算規則都封裝在對應的策略類裡面:

var performanceS = function(){};performanceS.prototype.calculate = function( salary ){    return salary * 4;};var performanceA = function(){};performanceA.prototype.calculate = function( salary ){    return salary * 3;};var performanceB = function(){};performanceB.prototype.calculate = function( salary ){    return salary * 2;};

接下來定義獎金類Bonus

var Bonus = function(){    this.salary = null;        //原始工資    this.strategy = null;    //績效等級對應的策略對象};Bonus.prototype.setSalary = function( salary ){    this.salary = salary;    //設定員工的原始工資};Bonus.prototype.setStrategy = function( strategy ){    this.strategy = strategy;    //設定員工績效等級對應的策略對象};Bonus.prototype.getBonus = function(){    //取得獎金數額    return this.strategy.calculate( this.salary );    //把計算獎金的操作委託給對應的策略對象};

在完成最終的代碼之前,我們再來回顧一下策略模式的思想:

定義一系列的演算法,把它們一個個封裝起來,並且使它們可以相互替換1。

這句話如果說得更詳細一點,就是:定義一系列的演算法,把它們各自封裝成策略類,演算法被封裝在策略類內部的方法裡。在客戶對Context發起請求的時候,Context總是把請求委託給這些策略對象中間的某一個進行計算。

1. “並且使它們可以相互替換”,這句話在很大程度上是相對於靜態類型語言而言的。因為靜態類型語言中有類型檢查機制,所以各個策略類需要實現同樣的介面。當它們的真正類型被隱藏在介面後面時,它們才能被相互替換。而在JavaScript這種“類型模糊”的語言中沒有這種困擾,任何對象都可以被替換使用。因此,JavaScript中的“可以相互替換使用”表現為它們具有相同的目標和意圖。

現在我們來完成這個例子中剩下的代碼。先建立一個bonus對象,並且給bonus對象設定一些原始的資料,比如員工的原始工資數額。接下來把某個計算獎金的策略對象也傳入bonus對象內部儲存起來。當調用bonus.getBonus()來計算獎金的時候,bonus對象本身並沒有能力進行計算,而是把請求委託給了之前儲存好的策略對象:

var bonus = new Bonus();bonus.setSalary( 10000 );bonus.setStrategy( new performanceS() );  //設定策略對象console.log( bonus.getBonus() );    // 輸出:40000    bonus.setStrategy( new performanceA() );  //設定策略對象console.log( bonus.getBonus() );    // 輸出:30000    

剛剛我們用策略模式重構了這段計算年終獎的代碼,可以看到通過策略模式重構之後,代碼變得更加清晰,各個類的職責更加鮮明。但這段代碼是基於傳統物件導向語言的模仿,下一節我們將瞭解用JavaScript實現的策略模式。

在5.1節中,我們讓strategy對象從各個策略類中建立而來,這是類比一些傳統物件導向語言的實現。實際上在JavaScript語言中,函數也是對象,所以更簡單和直接的做法是把strategy直接定義為函數:

var strategies = {    "S": function( salary ){        return salary * 4;    },    "A": function( salary ){        return salary * 3;    },    "B": function( salary ){        return salary * 2;    }}; 

同樣,Context也沒有必要必須用Bonus類來表示,我們依然用calculateBonus 函數充當Context來接受使用者的請求。經過改造,代碼的結構變得更加簡潔:

var strategies = {    "S": function( salary ){        return salary * 4;    },    "A": function( salary ){        return salary * 3;    },    "B": function( salary ){        return salary * 2;    }};var calculateBonus = function( level, salary ){    return strategies[ level ]( salary );};console.log( calculateBonus( 'S', 20000 ) );        // 輸出: 80000console.log( calculateBonus( 'A', 10000 ) );        // 輸出: 30000

在接下來的緩動動畫和表單驗證的例子中,我們用到的都是這種函數形式的策略對象。

本文摘自《JavaScript設計模式與開發實踐》

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.