Javascript设计模式总结之 -- 策略模式
Posted FQQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Javascript设计模式总结之 -- 策略模式相关的知识,希望对你有一定的参考价值。
1.写设计模式系列文章的背景
本系列文章主要是为了对javascript的设计模式做一个总结。各个网站中对设计模式都有介绍,也有专门的书。那么我写的原因是为了自己能够更清晰的理解,也为了对大家的学习有些不同角度的感悟。
我为什么会对设计模式越来越重视?是因为长时间的写代码后,需要再返回来看看自己之前写的代码跟现在的代码有什么不一样,或者说当前的代码比半年前、一年前好很多么? 如果没有感觉好很多,那么说明可能已经到达了这个阶段的天花板,需要拓宽自己的代码视野了。
拓宽自己的视野方式很多,比如阅读好框架的源码,能从中学到不少,但是我通过实践发现,阅读好的源码跟设计模式、算法都是相辅相成的,如果不熟悉各种设计模式和算法,那么框架源码中的一些写法就可能看不明白,或者只能在\'照葫芦画瓢\'
,无法理解透彻。这些感悟都是自己采坑踩出来的,所以我开始重视设计模式的深入研究,并力争不断的运用到代码中,这也是一个不断进步的过程,不是一蹴而就。所以大家有兴趣的话,也加入到设计模式的熟悉中来吧。
上面的背景我唠叨的差不多了,在后面的文章中,我会陆续把我知道的和运用过的设计模式写出来。 今天是设计模式的第一课-- 策略模式
。
模式
是什么? 模式就像在建房子的时候,按照之前的经验,先打地基,再搭框架,再填充墙砖,有先后,有重点...有了模式,建多高,多复杂的建筑,都不会出问题。 同时,模式并不是自己从头想出来的,模式更像是站在巨人的肩上,把前人积累出的经验活学活用。
如果没有模式,在初期也能勉强达到效果,比如我们每家都有衣服,如过衣服不是很多,我们随便堆在角落,使用的时候能找到。但如果衣服越来越多,有大人的,也有孩子的,那么找一件衣服,就需要耗费越来越多的时间和成本。所以衣柜的作用,能帮我分类,减少查找时间。同样,加入设计模式能够让代码越来越有条理性,虽然在使用时不能随心所欲的写,但是等到随着项目越来越大,时间越来越久的时候,好的设计模式能大大减少维护成本。所以代码和日常生活都是相通的。
再唠叨一句,好的代码是什么样的?面向对象的基本特性就是 继承
, 封装
, 多态
。落实到具体代码中,我们也在遵循一些最佳实践:
- 函数功能单一化,输入和输出结果的可预测。
- 代码逻辑稳定,尽量以后的增删改不对原来的代码做过多修改,因为对原有代码改的越多,越容易影响项目的稳定(OCP 开放-封闭原则)
- 通过继承更好的做复用,但是不要继承层级过多
- 。。。。
策略模式
策略模式的定义是:"定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。" 其实有点难懂,我理解可以从实际的生活中看这个策略
问题。 比如我们每天上班的时候,可以坐公交、可以坐地铁、可以开车,可以骑自行车,这其实就是一种策略选择问题,无非看的是经济成本和时间成本。
代码中也一样,因为人的思维还是喜欢线性,所以,在写代码的时候,如果没有经过训练,就愿意写一个巨大无比的方法,也不愿管有什么策略,都柔在一起,一堆if else 但是有些编程经验的的同学就知道这样写刚开始时比较爽,但是随着逻辑越来越庞大的时候,就会发现维护起来越来越麻烦,体现在:
- 忘了之前的这一大堆逻辑其中各个的作用是什么,继续加逻辑的时候就不好下手
- 每次增加,都可能对以前的逻辑产生负面影响,可能引入新bug
所以,我们会自觉的把逻辑进行功能性的拆分,其实这已经加入策略在里面了。
我理解的策略模式最佳使用场景:
1. 业务逻辑需要在几种算法中动态的进行选择
2. 当一个对象有很多的行为,但不想使用一堆条件判断语句来进行区分
案例1
比如说在app中,都有做任务提高用户存留的游戏或者活动,现有以下活动:
- 如果联系签到7天,可以在买某个物品时享受9.9折的优惠,
- 如果联系签到14天,某个物品则可享受9折优惠,
- 如果联系签到30天,某个物品则在享受8.8折优惠
按照正常思维逻辑,绝大部分程序猿,包括之前的我,都会用条件判断的方式来实现:
function discount(day, price) {
switch (day) {
case 7:
return price * 0.99;
case 14:
return price * 0.9;
case 30:
return price * 0.88
}
}
上面的这个discount
函数中的各个算法在活动不会改变的情况下没有任何不妥,但是如果现在要增加一个连续签到40天的,并且把连续签到7天的活动删除,那么我们在当前逻辑下,只能更改原函数。这就有违 开放-封闭
原则。也就是说类、函数等应该是可以扩展的,但是不可修改
那么根据策略模式,更好的实现可以是这样:
function Discount() {
// ...
// 各种逻辑...
}
Discount.rules = {
day7: 0.99,
day14: 0.9,
day30: 0.88
}
Discount.addRule = function(day, rule) {
Discount.rules[day] = rule
}
Discount.deleteRule = function(day) {
delete Discount.rules[day];
}
Discount.prototype.getPrice = function(day, price) {
return price * Discount.rules[day];
}
// 添加一个新的折扣规则
Discount.addRule(\'day10\', 0.95);
var hat = new Discount();
hat.getPrice(\'day7\', 1000);
hat.getPrice(\'day10\', 1000);
通过这种方式,以后的添加或删除,不需要对原函数进行直接的更改。对函数的封装和复用性会更好一些。
当然,我认为这些封装是根据需求的不断迭代而迭代的,如果项目逻辑就是非常简单,后面不会进行逻辑的增删改,那纯粹套这个模式也没什么必要。
案例二
除了算法可以使用策略模式,像代码中的一些逻辑判断,也可以使用,总之目的就是减少对原逻辑的暴力修改,提升逻辑的稳定性和向下兼容性。
网上也有不少例子,比如说在表单验证的时候,可能由于有多个input框,需要多个if else来进行验证。
// 伪代码
function validator() {
var nameInput = this.nameValue;
var ageInput = this.ageValue;
var telInput = this.telValue;
var addressInput = this.addressValue;
var simpleRegExp = /^1[3456789]\\d{9}$/
if (nameInput === \'\') {
this.ErrorMsg = \'请填写姓名\';
}
else if (ageInput === \'\' || typeof ageInput === \'number\') {
this.ErrorMsg = \'请正确填写年龄\'
}
else if (!simpleRegExp.test(telInput)) {
this.ErrorMsg = \'请填写正确的手机号\'
}
else if (addressInput === \'\') {
this.ErrorMsg = \'请填写地址\'
}
}
再次声明,我不认为上面的伪代码就不好,而是什么场景下,用什么样的模式更为易扩展和易维护.
那么什么情况下使用策略模式比较好?还是需要动态增减,而不想在原来代码中改来改去(这简直是重度代码洁癖者的福音有木有)
// 虽然这一堆if else并不是算法,但也可以包装起来,做到更好的OCP
function Validator() {
this.options = {
validateName: function(name) {
return name.trim() !== \'\';
},
validateAge: function(age) {
return typeof age === \'number\';
},
validateTel: function(tel) {
return /^1[3456789]\\d{9}$/.test(tel);
}
}
}
// 动态添加规则
Validator.prototype.addPattern = function(valiName, pattern) {
if (valiName in this.options) {
return;
}
this.options[valiName] = pattern;
}
// 具体进行规则匹配
Validator.prototype.validate = function(args) {
// {validateName: \'aaa\', validateAge: 24, validateTel: \'1234567\'}
var type = Object.prototype.toString.call(args);
var targetOptions = {};
if (type !== \'[object Object]\') {
throw new TypeError(\'expect type object, but got\' + type);
}
var keys = Object.keys(args);
for(var i =0; i< keys.length; i++) {
if (keys[i] in this.options) {
if(!this.options[keys[i]](args[i])){
// 如果某个验证失败
return false;
}
} else {
throw new Error(\'validator pattern is not found !\');
}
}
return true;
}
var va = new Validator();
va.validate({validateName: \'aaa\', validateAge: 24, validateTel: \'1234567\'}) // false
可以看到,比一般写法要麻烦不少,但是以后维护起来会方便很多。所以要不要用这个模式,还需要看具体需求。而且,这个模式在我看来也并不是一劳永逸的解决向下兼容问题。
比如说之前有个验证规则,在线上正常跑着,然后我们某个同学因为不知道,调用了delete方法,把这个验证规则干掉了,这时,也就会出错,无法保证向下兼容。
所以,代码逻辑划分的粒度需要根据实际需要来处理。 后面将会讲 装饰器模式
,这个模式在特定的情况下可能做到比较好的向下兼容。且看下一篇~~
以上是关于Javascript设计模式总结之 -- 策略模式的主要内容,如果未能解决你的问题,请参考以下文章