策略模式的定義是:定義一系列的算法(這些算法目標一致),把它們一個個封裝起來,而且使它們能夠相互替換。javascript
好比要實現從上海到廣州,既能夠坐火車,又能夠坐高鐵,還能夠坐飛機。這取決與我的想法。在這裏,不一樣的到達方式就是不一樣的策略,我的想法就是條件。html
以計算獎金爲例,績效爲S的年終獎是4倍工資,績效爲A的年終獎是3倍工資,績效爲B的年終獎是2倍工資。那麼這裏獎金取決於兩個條件,績效和薪水。最初編碼實現以下:java
const calculateBonus = (performanceLevel, salary) => {
if(performanceLevel === 'S') {
return salary * 4;
}
if(performanceLevel === 'A') {
return salary * 3;
}
if(performanceLevel === 'B') {
return salary * 2;
}
}
複製代碼
這段代碼十分簡單,但存在顯而易見的缺點。算法
策略模式是指定義一系列的算法,並將它們封裝起來,這很符合開閉原則。策略模式的目的就是將算法的使用和算法的實現分離出來。bash
一個基於策略模式的程序至少由兩部分組成。第一個部分是策略類,它封裝了具體的算法,並負責計算的具體過程。第二個部分是環境類Context,Context接受客戶的請求,隨後將請求委託給某一個策略類。要作到這點,Context中須要維持對某個策略對象的引用。app
如今使用策略模式來重構以上代碼,第一個版本是基於class,第二個版本是基於函數。dom
class PerformanceS {
calculate(salary) {
return salary * 4;
}
}
class PerformanceA {
calculate(salary) {
return salary * 3;
}
}
class PerformanceB {
calculate(salary) {
return salary * 2;
}
}
class Bonus {
constructor(strategy, salary) {
this.strategy = strategy;
this.salary = salary;
}
getBonus() {
if(!this.strategy) {
return -1;
}
return this.strategy.calculate(this.salary);
}
}
const bonus = new Bonus(new PerformanceA(), 2000);
console.log(bonus.getBonus()) // 6000
複製代碼
它沒有上述的三個缺點。在這裏,有三個策略類,分別是PerformanceS、Performance A、PerformanceB。這裏的context就是Bonus類,它接受客戶的請求(bonus.getBonus),將請求委託給策略類。它保存着策略類的引用。函數
上述中,每個策略都是class,實際上,class也是一個函數。這裏,能夠直接用函數實現。post
const strategies = {
'S': salary => salary * 4,
'A': salary => salary * 3,
'B': salary => salary * 2,
}
const getBonus = (performanceLevel, salary) => strategies[performanceLevel](salary)
console.log(getBonus('A', 2000)) // 6000
複製代碼
經過使用策略模式重構代碼,消除了程序中大片的條件語句。全部和獎金相關的邏輯都不在Context中,而是分佈在各個策略對象中。Context並無計算獎金的能力,當它接收到客戶的請求時,它將請求委託給某個策略對象計算,計算方法被封裝在策略對象內部。當咱們發起「得到獎金」的請求時,Context將請求轉發給策略類,策略類根據客戶參數返回不一樣的內容,這正是對象多態性的體現,這也體現了策略模式的定義--「它們能夠相互替換」。動畫
咱們的目標是編寫一個動畫類和緩動算法,讓小球以各類各樣的緩動效果在頁面中進行移動。
很明顯,緩動算法是一個策略對象,它有幾種不一樣的策略。這些策略函數都接受四個參數:動畫開始的位置s、動畫結束的位置e、動畫已消耗的時間t、動畫總時間d。
const tween = {
linear: (s, e, t, d) => { return e*t/d + s },
easeIn: () => { /* some code */ },
easeOut: () => { /* some code */ },
easeInOut: () => { /* some code */ },
}
複製代碼
頁面上有一個div元素。
<div id='div' style='position: absolute; left: 0;'></div>
複製代碼
如今要讓這個div動起來,須要編寫一個動畫類。
const tween = {
linear: () => { /* some code */ },
easeIn: () => { /* some code */ },
easeOut: () => { /* some code */ },
easeInOut: () => { /* some code */ },
}
class Animation {
constructor(dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null;
this.easing = null;
this.duration = null;
}
// 開始動畫
start(propertyName, endPos, duration, easing) {
this.startTime = Date.now();
// 初始化參數,省略其餘
const self = this;
// 循環執行動畫,若是動畫已結束,那麼清除定時器
let timer = setInterval(() => {
if(self.step() === false) {
clearInterval(timer);
}
}, 1000/60);
}
// 計算下一次循環到的時候小球位置
step() {
const now = Date.now();
if(now > this.startTime + this.duration) {
return false;
} else {
// 得到小球在本次循環結束時的位置並更新位置
// const pos = this.easing();
// this.update(pos);
}
}
update(pos) {
this.dom.style[propertyName] = pos + 'px';
}
}
複製代碼
具體實現略去。這裏的Animation類就是環境類Context,當接收到客戶的請求(更新小球位置 self.step()),它將請求轉發給策略內(this.easing()),策略類進行計算並返回結果。
策略模式指的是定義一系列的算法,而且把他們封裝起來。上述所說的計算獎金和緩動動畫的例子都封裝了一些策略方法。
從定義上看,策略模式就是用來封裝算法的。但若是僅僅將策略模式用來封裝算法,有些大材小用。在實際開發中,策略模式也能夠用來封裝一些的「業務規則」。只要這些業務規則目標一致,而且能夠替換,那麼就能夠用策略模式來封裝它們。以使用策略模式來完成表單校驗爲例。
<form action='xxx' id='form' method='post'>
<input type='text' name='username'>
<input type='password' name='passsword'>
<button>提交</button>
</form>
複製代碼
驗證規則以下:
const form = document.querySelector('form')
form.onsubmit = () => {
if(form.username.value === '') {
alert('用戶名不能爲空')
return false;
}
if(form.password.value.length < 6) {
alert('密碼不能少於6位')
return false;
}
}
複製代碼
這是一種很常見的思路,和最開始計算獎金同樣。缺點也是同樣。
第一步須要把這些校驗邏輯封裝成策略對象。
const strategies = {
isNonEmpty: (value, errMsg) => {
if(value === '') {
return errMsg
}
},
minLength: (value, errMsg) => {
if(value.length < minLength) {
return errMsg
}
}
}
複製代碼
第二步對錶單進行校驗。
class Validator {
constructor() {
this.rules = [];
}
add(dom, rule, errMsg) {
const arr = rule.split(':');
this.rules.push(() => {
const strategy = arr.shift();
arr.unshift(dom.value);
arr.push(errMsg);
return strategies[strategy].apply(dom, arr);
})
}
start() {
for(let i = 0, validatorFunc; validatorFunc = this.rules[i++];) {
let msg = validatorFunc();
if(msg) {
return msg;
}
}
}
}
const form = document.querySelector('form')
form.onsubmit = (e) => {
e.preventDefault();
const validator = new Validator();
validator.add(form.username, 'isNonEmpty', '用戶名不能爲空');
validator.add(form.password, 'minLength:6', '密碼長度不能小於6位');
const errMsg = validator.start();
if(errMsg) {
alert(errMsg);
return false;
}
}
複製代碼
上述例子中,校驗邏輯是策略對象,其中包含策略的實現函數。Validator類是Context,用於將客戶的請求(表單驗證)轉發到策略對象進行驗證。與計算獎金的Bonus不一樣的是,這裏並無將驗證參數經過構造函數傳入,而是經過validator.add傳入相關驗證參數,經過validator.start()進行驗證。
前三點正是開頭實現的計算獎金函數的缺點。 策略模式有一點缺點,不過並不嚴重。