首頁  >  文章  >  web前端  >  學習JavaScript設計模式(策略模式)_javascript技巧

學習JavaScript設計模式(策略模式)_javascript技巧

WBOY
WBOY原創
2016-05-16 15:29:401004瀏覽

何為策略?例如我們要去某個地方旅遊,可以根據具體的實際情況來選擇出行的路線。
1、策略模式的定義

如果沒有時間但是不在乎錢,可以選擇坐飛機。
如果沒有錢,可以選擇搭巴士或火車。
如果再窮一點,可以選擇騎自行車。
在程式設計中,我們也常常遇到類似的情況,要實現某一個功能有多種方案可以選擇。例如一個壓縮檔案的程序,既可以選擇zip演算法,也可以選擇gzip演算法。

定義:策略模式定義一系列的演算法,分別封裝起來,讓他們之間可以互相替換,此模式讓演算法的變化獨立於使用算飯的客戶.

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

2、年終獎項實例

許多公司的年終獎是根據員工的薪資基數和年底績效情況來發放的。例如,績效為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 ); // 输出:40000
calculateBonus( '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 ); //把计算奖金的操作委托给对应的策略对象
};

在完成最终的代码之前,我们再来回顾一下策略模式的思想:

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

这句话如果说得更详细一点,就是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。

“并且使它们可以相互替换”,这句话在很大程度上是相对于静态类型语言而言的。因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。而在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 ) ); // 输出: 80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出: 30000

3、实例再讲解

一个小例子就能让我们一目了然。
回忆下jquery里的animate方法.

$( div ).animate( {"left: 200px"}, 1000, 'linear' ); 
//匀速运动
$( div ).animate( {"left: 200px"}, 1000, 'cubic' ); 
//三次方的缓动

这2句代码都是让div在1000ms内往右移动200个像素. linear(匀速)和cubic(三次方缓动)就是一种策略模式的封装.

再来一个例子. 很多页面都会有个即时验证的表单. 表单的每个成员都会有一些不同的验证规则.

比如姓名框里面, 需要验证非空,敏感词,字符过长这几种情况。 当然是可以写3个if else来解决,不过这样写代码的扩展性和维护性可想而知。如果表单里面的元素多一点,需要校验的情况多一点,加起来写上百个if else也不是没有可能。

所以更好的做法是把每种验证规则都用策略模式单独的封装起来。需要哪种验证的时候只需要提供这个策略的名字。就像这样:

nameInput.addValidata({
 notNull: true,
 dirtyWords: true,
 maxLength: 30
})
而notNull,maxLength等方法只需要统一的返回true或者false,来表示是否通过了验证。

validataList = {
 notNull: function( value ){
 return value !== '';
 },
 maxLength: function( value, maxLen ){
 return value.length() > maxLen;
 }
}

可以看到,各种验证规则很容易被修改和相互替换。如果某天产品经理建议字符过长的限制改成60个字符。那只需要0.5秒完成这次工作。

大概内容就为大家介绍到这。

聊一聊题外话,马上2015年要过去了,大家的年终奖是不是很丰厚呀!!!

希望大家可以在这一年里有所收获,通过这篇文章也能有所收获,知道什么是策略模式,理解小编精心为大家准备的两个实例。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn