JS设计模式-策略模式
参考:https://www.cnblogs.com/chenwenhao/p/12354176.html
策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
设计模式是针对某一类问题的优雅解决方案。策略模式出现的场景是,调用者要根据当前不同条件判断选择执行不同的策略,而这些策略是对某一问题的不同处理方案。
初级的编码是,在策略调用方法中,根据不同的条件判断使用if...else等分支控制语句,在if代码块中书写策略的具体内容。如果业务需求很简单,只有两种解决方案,而且实现算法简单,这样写是没有问题的。
如果需要判断的策略超过3个,甚至十几种、几十种,调用方法中条件判断就显得太多,不利于阅读和维护。此时就需要考虑使用策略模式进行重构了。
重新分析问题场景,可以抽象出三个东西,策略调用、策略分发、策略定义。有了这个思想,不管代码如何实现,都是策略模式。
下来通过代码,一步步讨论代码实现:
先看下很多博客都会引用的栗子
var calculateBonus = function( performanceLevel, salary ){ if ( performanceLevel === 'A' ){ return salary * 4; } if ( performanceLevel === 'B' ){ return salary * 3; } if ( performanceLevel === 'C' ){ return salary * 2; } }; calculateBonus( 'B', 20000 ); // 输出:40000 calculateBonus( 'S', 6000 ); // 输出:24000
可以看出,我们提到的策略调用、策略分发、策略定义都在一个代码块中,随着业务逻辑的增加、算法复杂度的增加,函数会变得越来越庞大,难以维护,违反开放-封闭原则。
接下来,我们对它进行重构:
var performanceA = function( salary ){ return salary * 4; }; var performanceB = function( salary ){ return salary * 3; }; var performanceC = function( salary ){ return salary * 2; }; var calculateBonus = function( performanceLevel, salary ){ if ( performanceLevel === 'A' ){ return performanceA( salary ); } if ( performanceLevel === 'B' ){ return performanceB( salary ); } if ( performanceLevel === 'C' ){ return performanceC( salary ); } }; calculateBonus( 'A' , 10000 ); // 输出:40000
可以看出,虽然将策略定义分离出去了,但是策略调用和策略分发还在一个函数中,函数还还是存在越来越庞大的风险。
进一步重构:
① 策略定义
var performanceA = function() {}; performanceA.prototype.calculate = function (salary) { return salary * 4 } var performanceB = function(){}; performanceB.prototype.calculate = function( salary ){ return salary * 3; }; var performanceC = function(){}; performanceC.prototype.calculate = function( salary ){ return salary * 2; };
② 策略分发
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 performanceA() ); //设置策略对象 console.log( bonus.getBonus() ); // 输出:40000 bonus.setStrategy( new performanceB() ); //设置策略对象 console.log( bonus.getBonus() ); // 输出:30000
可以看出,将整个过程分成了三部分,需要增加策略时,在策略定义中添加,不需要修改之前的代码;策略分发也不需要改动;调用时需要指定具体的策略。
这此重构已经符合了我们提到的策略调用、策略分发、策略定义,是一个完整的,有扩展性,符合开放封闭原则的代码结构。
其实,思想是一样的,但具体代码还可以采用不同形式:
① 利用js对象来组织策略定义代码
var strategies = { "A": function( salary ){ return salary * 4; }, "B": function( salary ){ return salary * 3; }, "C": function( salary ){ return salary * 2; } };
② 利用js函数传参 来组织策略分发代码
var calculateBonus = function( level, salary ){ return strategies[level](salary) }; console.log( calculateBonus( 'A', 20000 ) ); // 输出: 80000 console.log( calculateBonus( 'B', 10000 ) ); // 输出: 30000
万变不离其宗,只要实现了使用、分发、定义的分离,采用何种数据结构组织代码都是ok的。
我实际vue项目开发中遇到的,策略模式除了对这种纯js逻辑的处理,对于同一个模板应用于多个业务的场景也非常适用。
① .vue文件 调用
computed: { instance: function () { return new moduleFactory(this.moduleName, this.type) }, },
② 策略定义
class ModuleFactory { constructor(moduleName, type) { this.moduleName = moduleName; this.type = type; if (this.moduleName == 'themeClassify') { switch (this.type) { case 1: return new DomainGroup(); case 2: return new Domain(); case 3: return new Theme(); } } else if (this.moduleName == 'customClassify') { switch (this.type) { case 1: return new Category(); case 2: return new Item(); case 3: return new Order(); case 4: return new SubOrder(); } } } } class ThemeClassify { constructor() { this.showAddBtn = true; this.showMoreInfo = true; this.groupCountClassName = 'group-count-3'; this.svcName = 'layerManageSvc'; } getQueryRequest(params, target) { return target.service.layerManageSvc.query(params) } getDelRequest(params, target) { return target.service.layerManageSvc.delete(params) } } class CustomClassify { constructor() { this.showAddBtn = true; this.showMoreInfo = false; this.groupCountClassName = 'group-count-4'; this.svcName = 'layerManageSvc'; } getQueryRequest(params, target) { return target.service.layerManageSvc.query(params) } getDelRequest(params, target) { return target.service.layerManageSvc.delete(params) } } class DomainGroup extends ThemeClassify { constructor() { super(); this.title = '主题域分组'; } getSaveRequest(oData, target) { return target.service.layerManageSvc.postDomainGroup(oData) } } class Domain extends ThemeClassify { constructor() { super(); this.title = '主题域'; } getSaveRequest(oData, target) { return target.service.layerManageSvc.postDomain(oData) } } class Theme extends ThemeClassify { constructor() { super(); this.title = '主题'; this.showAddBtn = false; } getSaveRequest(oData, target) { return target.service.layerManageSvc.postBusiness(oData) } } class Category extends CustomClassify { constructor() { super(); this.title = '类'; } } class Item extends CustomClassify { constructor() { super(); this.title = '项'; } } class Order extends CustomClassify { constructor() { super(); this.title = '目'; } } class SubOrder extends CustomClassify { constructor() { super(); this.title = '细目'; this.showAddBtn = false; } } export default ModuleFactory;
可以看出,我的策略分发使用类的构造函数实现的,策略定义也是使用类定义的,而且对于相同的策略类使用了继承特性。
至此,策略模式走完了他井猜的一生。