策略模式 是一种行为设计模式,能让我们定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。
试想这样一个场景,在实现编译器中的四则运算时,将符号(加减乘除)和两个参与运算的数字传入函数中,返回对应的结果
最初代码:
const calc = function(type, num1, num2) {
if(type === '+'){
return num1 + num2
}
if(type === '-') {
return num1 - num2
}
if(type === '*'){
return num1 * num2;
}
if(type === '/'){
return num1 / num2;
}
}
calc('+', 1, 2); // 3
calc('-', 3, 1); // 2
calc 函数缺乏弹性。如果未来又有引入了取模运算,那必须深入到 calc 函数的内部实现,这违反了开放-封闭原则。
下面我们使用策略模式重构这段代码。一个基于策略模式的程序至少包含两部分:1. 一组策略类,封装了具体的算法与计算过程;2. 环境类 Context,接收客户的请求,将请求委托给某一个策略类。
// 一组策略类
const calcSum = function() {}
calcSum.prototype.calc = function(num1, num2) {
return num1 + num2;
}
const calcDifference = function() {}
calcDifference.prototype.calc= function(num1, num2) {
return num1 - num2;
}
const calcQuadrature = function() {}
calcQuadrature.prototype.calc = function(num1, num2) {
return num1 * num2;
}
const calcQuotient = function() {}
calcQuotient.prototype.calc = function(num1, num2) {
return num1 / num2;
}
// 定义运算类
const CalcRes = function(){
this.num1 = null;
this.num2 = null;
this.strategy = null;
}
CalcRes.prtotype.setOperands = function(num1, num2){
this.num1 = num1;
this.num2 = num2;
}
CalcRes.prototype.setStrategy = function(strategy) {
this.strategy = strategy;
}
CalcRes.prototype.getRes = function(){
return this.strategy.calc(this.num1, this.num2)
}
// 使用
const res = new CalRes();
res.setOperands(1, 2);
res.setStrategy(new calcSum()); // 设置策略对象
console.log(res.getRes()); // 3
res.setStrategy(new calcDifference()); // 设置策略对象
console.log(res.getRes()); // -1
上面的实现是让 strategy 对象从类中创建,这是模拟面向对象语言的实现。JavaScript 中的函数也是对象,于是可以把 strategy 直接定义为函数。
const strategies = {
add: function(num1, num2){
return num1 + num2;
},
subtract: function(num1, num2){
return num1 - num2;
},
multiply: function(num1, num2){
return num1 * num2;
},
divide: function(num1, num2){
return num1 / num2;
},
}
const calcRes = function(type, num1, num2){
return strategies[type](num1, num2);
}
console.log(calcRes('add', 1, 2)); // 3
console.log(calcRes('multiply', 1, 2)); // 2
在 JavaScript 这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并把它传递到另一个函数中。
在之前的例子中,为了清楚地表示这是一个策略模式,我们特意使用了 strategies 这个名字。如果去掉 strategies,我们仍然要认出这是一个策略模式。
const add = function(num1, num2) {
return num1 + num2;
}
const subtract = function(num1, num2){
return num1 - num2;
}
const multiply = function(num1, num2){
return num1 * num2;
}
const divide = function(num1, num2){
return num1 / num2;
}
const calcRes = function(fn, num1, num2) {
return fn(num1, num2);
}
calcRes(add, 1, 2); // 3
优点:
策略模式也有缺点,但缺点并不严重。要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。