Javascript设计模式(四)策略模式

栏目: 后端 · 发布时间: 6年前

内容简介:策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。在现实中,如果我们想去某个地方旅游,可以根据实际情况有多种路线现在以年终奖的计算为例

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

在现实中,如果我们想去某个地方旅游,可以根据实际情况有多种路线

  • 如果没有时间但是不在乎钱,可以选择飞机
  • 如果没有钱,可以选择大巴活着火车
  • 如果再穷一点,可以选择骑自行车

使用策略模式计算奖金

现在以年终奖的计算为例

公司年终奖根据员工的 工资基数年底绩效 来发放

  • 绩效S,四倍年终奖
  • 绩效A,三倍年终奖
  • 绩效B,二倍年终奖

最初的实现

var calculateBonus = function(performanceLevel, salary) {
	if (performanceLevel === 'S') {
		return salary*4
	}
	if (performanceLevel === 'A') {
		return salary*3
	}
	if (performanceLevel === 'B') {
		return salary*2
	}
}
calculateBonus('B', 2000) // 4000
calculateBonus('S', 2000) // 8000
复制代码

这段代码简单,但是存在显而易见的缺点

  1. 函数比较庞大,包含很多if-else语句,这些语句需要覆盖所有的逻辑分支
  2. 缺乏弹性,如果想新增绩效C,就得深入函数内部实现,违反开放-封闭原则
  3. 算法的复用性差

策略模式的实现

var strategies = {
	"S": function(salary) {
		return salary * 4
	},
	"A": function(salary) {
		return salary * 3
	},
	"B": function(salary) {
		return salary * 2
	}				
}
var calculateBonus = function(level, salary) {
	return strategies[level](salary)
}
console.log(calculateBonus('S', 2000)) // 8000
console.log(calculateBonus('B', 2000)) // 4000
复制代码

通过使用策略模式重构代码,消除来原程序中分支语句。所有计算奖金有关的逻辑分布在策略对象中,每个策略对象的算法已被各自封装在对象内部,当我们对这些策略对象发出“计算奖金”的请求时,它们会返回各自的计算结果,这不仅是多态性的体现,也是“自由交换”的目的。

使用策略模式实现缓动动画

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
	</head>
	<body>
	<div id="div" style="background: #141527;display: inline-block;position: relative;">我说div</div>
	<script type="text/javascript">
/**
 * 缓动算法
 * @t 已消耗的时间
 * @b 小球原始位置
 * @c 小球目标位置
 * @d 动画持续的总时间
 */
var tween = {
        Linear: function(t,b,c,d){ return c*t/d + b; },
	easeIn: function(t,b,c,d){ return c*(t/=d)*t + b;},
	easeOut: function(t,b,c,d){ return -c *(t/=d)*(t-2) + b;},
	easeInOut: function(t,b,c,d){
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	}
}

/**
 * 定义Animate类
 */
var Animate = function(dom) {
	this.dom = dom;			// 运动的元素
	this.startTime = 0;		// 动画开始时间
	this.startPos = 0;		// 元素初始位置
	this.endPos = 0;		// 元素结束位置
	this.propertyName = '';		// 实现动画的元素属性
	this.easing = null;		// 缓动算法
	this.duration = 0;		// 动画持续的时间
};

/**
 * 启动动画
 */
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];		// 缓动算法
	var that = this;
	var timed = setInterval(function() {
		// 执行每帧操作
		if(that.step() === false) {		// 动画已结束
			clearInterval(timed);
		}
	}, 19);
};

// 判断当前动画状态,调用update
Animate.prototype.step = function() {
	var t = new Date();		// 执行动画的当前时间
	if( t.getTime() >= this.startTime.getTime() + 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';
};

var div = document.getElementById('div')
var animate = new Animate(div)
animate.start('top', 500, 1000, 'easeInOut')

	</script>	
	</body>
</html>
复制代码

用策略模式实现表单验证

从定义上看,策略模式就是用来封装算法的。但是如果仅仅用来封装算法,未免有点大材小用。在实际业务中,策略模式也可以用来封装一系列的“业务规则”。只要业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们。

普通版本的表单验证

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title></title>
</head>
<body>
<form action="http://xxx.com/register" id="registerForm" method="post">
	请输入用户名<input type="text" name="username"/><br />
	请输入密码<input type="text" name="password"/><br />
	请输入手机号<input type="text" name="phonenumber"/><br />
	<input type="submit" value="提交" style="padding: 10px 20px;">
</form>
<script type="text/javascript">
        var registerForm = document.getElementById('registerForm')
        
        registerForm.onsubmit = function() {
	if (registerForm.username.value === '') {
		alert('用户名不能为空')
		return false
	}
	if (registerForm.password.value.length < 6) {
		alert('密码长度不能小于6位')
		return false
	}
	if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phonenumber.value) ){
		alert('手机号码格式不正确')
		return false
	}						
}
</script>	
</body>
</html>
复制代码

这是一种很常见的编码方式,可以看到缺点和计算奖金一摸一样

用策略模式重构表单验证

  1. 很明显第一步我们需要将验证逻辑封装成策略对象
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
		}
	}
}
复制代码
  1. 接下来实现 Validator 类,负责接受用户的请求并委托给 strategies
var Validator = function() {
	//保存校验规则
	this.cache = [] 
}

// 添加校验
Validator.prototype.add = function(dom, rules) {
	var self = this
	// 遍历校验规则
	for(var i = 0, rule; rule = rules[i++];) { 
		(function(rule){
			//把strategy和参数分开
			var strategyAry = rule.strategy.split(':') 	
			var errorMsg = rule.errorMsg	
			// 把校验的步骤用空函数包装起来,并且放入cache
			self.cache.push(function(){	
				// 挑选出校验规则
				var strategy = strategyAry.shift()
				// 把input的value添加进参数列表
				strategyAry.unshift(dom.value)		
				// 把errorMsg添加进参数列表
				strategyAry.push(errorMsg)			
				return strategies[strategy].apply(dom, strategyAry)
			})
		})(rule)
	}
}

// 启动校验
Validator.prototype.start = function() {
	for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) { 
		// 开始校验,并取得校验后的结果
		var errorMsg = validatorFunc() 
		if (errorMsg) {
			return errorMsg
		}
	}
}

复制代码
  1. 接下来就是调用了
var registerForm = document.getElementById('registerForm')

var validataFunc = function() {
	var validator = new Validator()
	validator.add(registerForm.username, [
			{
				strategy: 'isNonEmpty',
				errorMsg: '用户名不能为空'
			},
			{
				strategy: 'minLength:10',
				errorMsg: '用户名长度不能小于10位'						
			}
		]
	)
	validator.add(registerForm.password, [
			{
				strategy: 'minLength:6',
				errorMsg: '密码长度不能小于6位'						
			}
		]
	)
	validator.add(registerForm.phonenumber, [
			{
				strategy: 'isMobile',
				errorMsg: '手机号码格式不正确'						
			}
		]
	)								
	var errorMsg = validator.start()
    return errorMsg							
}

var sub = document.querySelector('input[type="submit"]')
sub.onclick = function() {
	var errorMsg = validataFunc()
	if (errorMsg) {
		console.error(errorMsg)
		return false
	}
}
复制代码

使用策略模式重构代码之后,我们不仅通过“配置”的方式就可以完成一个表单的校验,这些规则也可以复用在程序的任何地方,还能以插件的形式,方便地移植到其他项目中。并且新增或者修改规则也是毫不费力的。

策略模式的优缺点

优点

  1. 策略模式利用组合,委托和多态等技术思想,可以有效避免多重条件选择语句。
  2. 策略模式提供了对开放-封闭原则的完美支持。将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
  3. 策略模式中的算法也可以复用在系统中的其他地方。
  4. 在策略模式中利用组合和委托让Content拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

缺点

  1. 使用策略对象会增加很多策略类或者策略对象,但实际上比把这些逻辑放在Content更好。
  2. 策略模式会向用户暴露所有实现细节,这其实是违反最少知识原则。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

云攻略

云攻略

马克·贝尼奥夫、卡莱尔·阿德勒 / 徐杰 / 海天出版社 / 2010年8月 / 36.00元

Apple、Google、甲骨文、腾讯 都已投入了云的怀抱, 你还在等什么? 快来加入我们! 最初,Salesforce.com 只是一间小小的租赁公寓 在短短10年内 它已成长为 世界上发展最快、最具创新力的 产业变革领导者 曾经,这是个软件为王的时代。 现在,这是个云计算的新时代。 NO SOFTWARE 抛弃软件的......一起来看看 《云攻略》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

URL 编码/解码
URL 编码/解码

URL 编码/解码