什么是策略模式?
策略模式(Strategy Pattern) 是一种行为设计模式。它定义一系列的策略算法,把它们一个个封装起来,每个策略都可以根据当前场景相互替换,从而将策略的实现和使用进行分离。 一个基于策略模式的程序至少由两部分组成:
- 策略类(不变的内容),策略类封装了具体的算法,并负责具体的计算过程
- 环境类(变化的内容),Context,Context 接受客户的请求,随后 把请求委托给某一个策略类
策略模式的优点
- 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句
- 策略模式提供了开放-封闭原则,使代码更容易理解和扩展
- 策略模式中的代码可以复用
关于开放封闭原则JavaScript五大原则之开闭原则
使用
我们用一个简单的例子引入
如果我们要对实现两个数的加减乘除操作,一般我们会写出下面的代码
function Calculate(command, num1, num2) {
if (command === 'add') {
return num1 + num2
} else if (command === 'sub') {
return num1 - num2
} else if (command === 'mul') {
return num1 * num2
} else if (command === 'div') {
return num1 / num2
}
}
这样一看, 会有非常多的if-else , 这是在判断语句不多的情况下使用是这样的, 我们可以先借这个例子引入策略模式
- 首先我们定义策略类,即 实现加减乘除操作
- 我们再定义一个环境类, 用于把操作委托给某一个策略类
定义策略类
// 定义策略类
class Add {
execute(num1, num2) {
return num1 + num2
}
}
class Sub {
execute(num1, num2) {
return num1 - num2
}
}
class Mul {
execute(num1, num2) {
return num1 * num2
}
}
class Div {
execute(num1, num2) {
return num1 / num2
}
}
// 定义一个环境类
class Claculate {
constructor() {
this.strategy = null
}
setStratrgy(strategy) {
this.strategy = strategy // 更改策略对象, 即要实施加减乘除中的什么操作
}
calculateResult(num1, num2) { // 委托策略类
return this.strategy.execute(num1, num2)
}
}
const calculate = new Claculate()
calculate.setStratrgy(new Add()) // 更改策略对象
console.log(calculate.calculateResult(6, 3)); // 调用该车略对象的execute函数
calculate.setStratrgy(new Sub()) // 更改策略对象
console.log(calculate.calculateResult(6, 3)); // 调用该车略对象的execute函数
代码运行结果如图:
实战
在表单验证的时候,我们经常会写出这样的代码
let registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
if(registerForm.userName.value === '') {
alert('用户名不能为空');
return;
}
if(registerForm.password.value.length < 6) {
alert("密码的长度不能小于6位");
return;
}
if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
alert("手机号码格式不正确");
return;
}
}
从上述可有看到,函数内部包含过多if...else,并且后续改正的时候,需要在函数内部添加逻辑,违反了开放封闭原则
而如果使用策略模式,就是先定义一系列算法,把它们一个个封装起来,将不变的部分和变化的部分隔开,如下:
定义表单
<form action="javascript:;" class="form">
请输入用户名称:<input type="text" name="userName" />
请输入用户密码:<input type="password" name="password">
请输入用户电话:<input type="text" name="phoneNumber" />
<button class="onSubmit">submit</button>
</form>
定义策略类第一步确定不变的内容,即策略规则对象,如下:
// 第一步,定义策略类,策略类封装了具体的算法,并负责具体的计算过程,确定不变的内容,即策略规则对象,这里指校验规则
const strategies = {
// 不能为空
isNotEmpty: function (value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
// 手机号码格式
mobileFormat: function (value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
}
定义环境类然后找出变的地方,作为环境类context,负责接收用户的要求并委托给策略规则对象,如下Validator类:
function Validator() {
this.ache = [] // 保存效验结果
this.strategy = null // 对应哪个策略
}
// 添加哪些表单需要用什么样的校验规则去校验,以及错误信息是什么
Validator.prototype.add = function (dom, rules, errMsg) {
this.ache.push(function () {
// 当 rules 为 minLength:6
let str = rules.split(':')
// 更改策略对象, 即要去校验哪一条规则,如 isNotEmpty, minLength
this.strategy = str.shift()
str.unshift(dom.value)
str.push(errMsg)
// 最后str 形如 [registerForm.password.value ,6, '密码长度不能小于6位']
// 或 [ registerForm.userName.value ,'用户名不能为空']
return strategies[this.strategy].apply(dom, str) // 返回校验结果
})
}
// 开始校验
Validator.prototype.star = function () {
for (let i = 0, validator; validator = this.ache[i++];) { // 遍历cache里面的校验函数
let errMsg = validator() // 开始效验 并取得效验后的返回信息
if (errMsg) return errMsg // 如果有确切的返回值,说明校验没有通过
}
}
通过validator.add方法添加校验规则和错误信息提示,使用如下:
function validatorFn() {
const validator = new Validator()
validator.add(registerForm.userName, 'isNotEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位')
validator.add(registerForm.phoneNumber, 'mobileFormat', '手机号码格式不正确')
const errMsg = validator.star()
return errMsg
}
const btn = document.querySelector('.onSubmit')
btn.addEventListener('click', () => {
const msg = validatorFn()
msg && (console.log(msg))
})
最后结果 用户名为空时
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<form action="javascript:;" class="form">
请输入用户名称:<input type="text" name="userName" />
请输入用户密码:<input type="password" name="password">
请输入用户电话:<input type="text" name="phoneNumber" />
<button class="onSubmit">submit</button>
</form>
</head>
<body>
<script>
const registerForm = document.querySelector('.form')
// 第一步,定义策略类,策略类封装了具体的算法,并负责具体的计算过程,确定不变的内容,即策略规则对象,这里指校验规则
const strategies = {
// 不能为空
isNotEmpty: function (value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
// 手机号码格式
mobileFormat: function (value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
}
function Validator() {
this.ache = [] // 保存效验结果
this.strategy = null // 对应哪个策略
}
// 添加哪些表单需要用什么样的校验规则去校验,以及错误信息是什么
Validator.prototype.add = function (dom, rules, errMsg) {
this.ache.push(function () {
// 当 rules 为 minLength:6
let str = rules.split(':')
// 获取对应策略
this.strategy = str.shift()
str.unshift(dom.value)
str.push(errMsg)
// 最后str 形如 [registerForm.password.value ,6,'密码长度不能小于6位'] 或 [ registerForm.userName.value ,'用户名不能为空']
return strategies[this.strategy].apply(dom, str) // 返回校验结果
})
}
// 开始校验
Validator.prototype.star = function () {
for (let i = 0, validator; validator = this.ache[i++];) { // 遍历cache里面的校验函数
let errMsg = validator() // 开始效验 并取得效验后的返回信息
if (errMsg) return errMsg // 如果有确切的返回值,说明校验没有通过
}
}
function validatorFn() {
const validator = new Validator()
validator.add(registerForm.userName, 'isNotEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位')
validator.add(registerForm.phoneNumber, 'mobileFormat', '手机号码格式不正确')
const errMsg = validator.star()
return errMsg
}
const btn = document.querySelector('.onSubmit')
btn.addEventListener('click', () => {
const msg = validatorFn()
msg && (console.log(msg))
})
</script>
</body>
</html>