策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
目的是将算法的使用和算法的实现分离开来;一个基本策略模式的程序至少有两部分组成,一部分是策略类,策略类封装了具体的算法,并负责具体的计算过程。第二部分是环境类Context,Context接收请求,随后把请求委托给某个策略类。要做到这点,需要Context要维持对某个策略对象的引用。
模仿静态语言的传统面向对象的实现
// 策略类S 算法实现
var performanceS = function(){};
performanceS.prototype.calculate = function(salary){return salary*4;};
// 策略类A 算法实现
var performanceA = function(){};
performanceA.prototype.calculate = function(salary){return salary*3;};
// 策略类B 算法实现
var performanceB = function(){};
performanceB.prototype.calculate = function(salary) {return salary*2;};
// Context 接收请求实现
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);};
var bonus = new Bonus();
bonus.setSalary(10000);
bonus.setStrategy(new performanceS());
console.log(bonus.getBonus())
bonus.setStrategy(new performanceA());
console.log(bonus.getBonus())
JS 版本的策略模式 如上,传统的策略对象都是基于某个策略类创建的,但是js中,函数也是对象,所以可以直接将策略定义为函数
// 具体的策略及其计算过程用函数实现,然后直接封装在一个对象中
var strategies = {
’S’: function(salary){
return salary * 4;
},
‘A’: function(salary){
return salary * 3;
},
‘B’: function(salary){
return salary * 2;
}
};
// Context 接收用户请求,则也是通过定义一个对象去返回具体的结果
var calculateBonus = function(level, salary) {
return strategies[level](salary);
}
calculateBonus(’S’, 20000); // 80000
利用策略模式写一个动画类
const tween = {
linear: (t, b, c, d) => {
return (c * t) / d + b;
},
easeIn: (t, b, c, d) => {
return c * (t /= d) * t + b;
},
strongEaseIn: (t, b, c, d) => {
return c * (t /= d) * t * t * t * t + b;
},
strongEaseOut: (t, b, c, d) => {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
sineaseIn: (t, b, c, d) => {
return c * (t /= d) * t * t + b;
},
sineaseOut: (t, b, c, d) => {
return c * ((t = t / d - 1) * t * t + 1) + b;
}
};
const Animate = function(dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null;
this.easing = null;
this.duration = null;
};
Animate.prototype.start = function(propertyName, endPos, duration, easing) {
this.startTime = +new Date();
this.startPos = this.dom.getBoundingClientRect()[propertyName];
this.propertyName = propertyName;
this.endPos = endPos;
this.duration = duration;
this.easing = tween[easing];
const self = this;
const timeId = setInterval(() => {
if (self.step() === false) {
clearInterval(timeId);
}
}, 19);
};
Animate.prototype.step = function() {
const t = +new Date();
if (t >= this.startTime + this.duration) {
this.update(this.endPos);
return false;
}
var pos = this.easing(
t - this.startTime,
this.startPos,
this.endPos - this.startPos,
this.duration
);
this.update(pos);
};
Animate.prototype.update = function(pos) {
this.dom.style[this.propertyName] = pos + "px";
};
const div = document.getElementById("haha");
console.log(div);
const animate = new Animate(div);
animate.start("right", 100, 1000, "strongEaseOut");
策略模式应用于表单提交
// 假设有这样的一个表单
<form id="registerForm" method="post">
请输入用户名: <input type="text" name="userName" />
<br />
请输入密码: <input type="text" name="password" />
<br />
请输入手机号: <input type="text" name="phoneNumber" />
<br />
<button>提交</button>
</form>
// 策略算法具体实现(具体业务)
var strategies = {
isNonEmpty: function(value, errorMsg) {
if (value === "") {
return errorMsg;
}
},
minLength: function(value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
isMobile: function(value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
// Context 接收客户请求,给出结果
var Validator = function() {
this.cache = []; // 设置缓存策略执行结果函数
};
Validator.prototype.add = function(dom, rules) {
var self = this;
for (var i=0, rule; rule=rules[i++];) {
(function(rule){
var strategyAry = rule.strategy.split(':');
var errorMsg = rule.errorMsg;
self.cache.push(function() {
var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom, strategyAry);
});
})(rule);
}
};
Validator.prototype.start = function() {
for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
var msg = validatorFunc();
if (msg) {
return msg;
}
}
};
// 调用校验函数(context),实例化校验类生成一个校验实例,添加对应的参数规则
var validataFunc = function(registerForm) {
var validator = new Validator();
validator.add(
registerForm.userName,
[
{strategy: 'isNonEmpty', errorMsg: "用户名不能为空"},
{ strategy: "minLength:10", errorMsg: "用户名长度不能少于10位"}
]
);
validator.add(
registerForm.password,
[{strategy: "minLength:3", errorMsg: "密码长度不能少于3位" }]
);
validator.add(
registerForm.phoneNumber,
[{strategy: "isMobile", errorMsg: "手机号码不正确" }]
);
var errorMsg = validator.start();
return errorMsg;
};
};
// 真正使用仅有validataFunc方法足以完成校验
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function() {
var errorMsg = validataFunc(registerForm);
if (errorMsg) {
alert(errorMsg);
return false;
}
};
简单总结下策略模式的优缺点: 优点: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
缺点: 1 使用策略模式会在程序中增加许多策略类或者策略对象 2 要使用策略模式,必须了解所有的策略,才能选择一个合适的策略,这是违反最少知识原则的
用javascript的函数的形式实现策略模式,这是一种隐式的策略模式,请看利用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函数发出调用信息时, 这些函数会返回不同的执行结果。
var S = function( salary ){
return salary * 4;
};
var A = function( salary ){
return salary * 3;
};
var B = function( salary ){
return salary * 2;
};
var calculateBonus = function( func, salary ){
return func( salary );
}
calculateBonus( S, 10000 ); // 输出:40000
总结: 本章我们既提供了接近传统面向对象语言的策略模式实现, 也提供了更适合 JavaScript 语言 的策略模式版本。在 JavaScript 语言的策略模式中,策略类往往被函数所代替, 这时策略模式就 成为一种“隐形”的模式。尽管这样,从头到 尾地了解策略模式,不仅可以让我们对该模式有更 加透彻的 了解,也可以使我们明白使用函数的好处。