JavaScript设计模式与开发实践(二)策略模式

策略模式

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

面向对象的策略模式

在上一篇提到过一种抽离执行和具体实现的例子就是一个典型的面向对象思想实现的策略模式,各种登录接口的类可以称为一组 策略类 ,而调用他的Login称为 环境类Context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
enum LoginType {
WeChat,
TaoBao,
TikTok,
}

class WeChatHandle{
handler(){
console.log(LoginType.WeChat)
}
}

class TaoBaoHandle{
handler(){
console.log(LoginType.TaoBao)
}
}

class TikTokHandle{
handler(){
console.log(LoginType.TikTok)
}
}

function Login(API){
API.handler()
}

Login(new TikTokHandle())

JavaScript中的策略模式

在JavaScript中不必使用类来实现一组策略类,我们完全可以把策略通过键值的方式保存在对象中,同样可以实现将实际逻辑跟执行分离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const LoginType = {
WeChat: "WeChat",
TaoBao: "TaoBao",
TikTok: "TikTok",
}

function Login(callback) {
callback()
}

const API = {}
Object.keys(LoginType).forEach(item => {
API[LoginType[item]] = () => {
console.log(LoginType[item]);
}
})
Login(API[LoginType.WeChat])

再比如一个表单校验的例子,假如我们要实现一个对表单中的某一个字段进行诸如字符长度,敏感符号之类校验的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName" />
请输入密码:<input type="text" name="password" />
请输入手机号码:<input type="text" name="phoneNumber" />
<button>提交</button>
</form>
<script>
const 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>

通过策略模式将代码分为策略类和环境类,并进行一些优化。

  1. 通过策略类避免繁琐的if else语句
  2. 通过Validator构造函数对每个实例维护一个规则数组,通过Validator实例的add方法添加校验规则 通过调用Validator实例的start方法对规则依次进行校验,实现简单的可插拔的校验规则模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const 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;
}
}
};

var Validator = function () {
this.cache = []; // 保存校验规则
};

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

Validator.prototype.start = function () {
for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
if (msg) { // 如果有确切的返回值,说明校验没有通过
return msg;
}
}
};

const validataFunc = function () {
const validator = new Validator(); // 创建一个 validator 对象
// 添加一些校验规则
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于 6 位');
validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
const errorMsg = validator.start(); // 获得校验结果
return errorMsg; // 返回校验结果
}

const registerForm = document.getElementById('registerForm');

registerForm.onsubmit = function () {
const errorMsg = validataFunc(); // 如果 errorMsg 有确切的返回值,说明未通过校验
if (errorMsg) {
alert(errorMsg);
return false; // 阻止表单提交
}
};

小结

策略模式的优点:

  1. 利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  2. 提供了对开放封闭原则的完美支持,将算法封装在策略类中,使得它们易于切换,易于扩展。
  3. 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复代码。
  4. 在策略模式中利用组合和委托来让Context拥有执行算法的能力(如上述代码中的 Validator构造函数 ),这也是继承的一种更轻便的替代方案。

策略模式的缺点:

  1. 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context 中要好。
  2. 要使用策略模式,必须了解所有的策略,必须了解各个策略之间的不同点, 这样才能选择一个合适的策略。

JavaScript设计模式与开发实践(二)策略模式
https://jing-jiu.github.io/jing-jiu/2023/01/05/notebooks/JavaScript设计模式与实践/设计模式(二)/
作者
Jing-Jiu
发布于
2023年1月5日
许可协议