table of contents

1概述

策略模式,是一种封装算法与规则的设计模式。

我们在学习封装的终极奥义时已经知道,封装就是提炼出来相同点,将不同点作为变量。

当不同点是一段逻辑、一个算法、一个规则时,我们就可以进一步将这个不同点进行封装。

这就是策略模式解决问题的思路。

2计算员工奖金

公司里每位员工的奖金都不一样。

员工的奖金,由员工基本工资与员工等级相关。

奖金 = 基本工资 * 等级对应的倍数

  • A 级 -> 5 倍
  • B 级 -> 4 倍
  • C 级 -> 3 倍
  • D 级 -> 2 倍
  • E 级 -> 1 倍

于是,我们就可以封装一个计算奖金的方法如下

code.ts
1
function getBouns(base, level) {
2
if (level == 'A') {
3
return base * 5
4
}
5
if (level == 'B') {
6
return base * 4
7
}
8
if (level == 'C') {
9
return base * 3
10
}
11
if (level == 'D') {
12
return base * 2
13
}
14
if (level == 'E') {
15
return base * 1
16
}
17
}

有了这个方法,计算奖金就很简单

code.ts
1
const p1 = {
2
name: '张三',
3
base: 1000,
4
level: 'A'
5
}
6
p1.bouns = getBouns(p1.base, p1.level)
7
8
console.log(p1)

这样封装虽然很简单,但是存在潜在的风险:奖金的计算方式,随时可能会改变

当奖金的计算方式发生了变化,新增了更多的员工等级,我们就不得不修改 getBouns 方法。

因此我们需要对该方法进行进一步的提炼,把该方法中的变量:奖金的计算方式提炼出来。这样,我们就不担心变量发生改变了。

此处,我们需要关注两个地方

  • 变量:奖金的计算方式,是一段逻辑,可以封装为一个函数
  • 奖金的计算方法:与员工等级是一对一的映射关系

因此,我们应该建立一个员工等级与奖金计算方式的隐射关系

code.ts
1
const map = {
2
A: function(base) {
3
return base * 5
4
},
5
B: function (base) {
6
return base * 4
7
},
8
C: function (base) {
9
return base * 3
10
},
11
D: function (base) {
12
return base * 2
13
},
14
E: function (base) {
15
return base * 1
16
},
17
}

即使以后有所修改,我们只需要修改该配置文件或者配置表即可。

员工的奖金依据该配置进行计算

code.ts
1
function getBouns(base, level) {
2
return map[level](base)
3
}
code.ts
1
const p2 = {
2
name: '李四',
3
base: 1200,
4
level: 'B'
5
}
6
p2.bouns = getBouns(p2.base, p2.level)
7
8
console.log(p2)

3表单验证

表单验证是一个非常常见的案例,我们也可以使用策略模式来解决规则的验证问题。

借鉴 antdesign 的表单规则,假设我们从组件中,获取得到的字段数据与其对应的规则如下

code.ts
1
2
// 从组件中获取到的字段内容,其中包括了具体的值,与传入的校验规则
3
const fields = {
4
username: {
5
value: '张三',
6
rules: [{required: true, message: '该字段为必填'}, {max: 10}]
7
},
8
password: {
9
value: '123',
10
rules: [{required: true}, {min: 6}]
11
},
12
phone: {
13
value: '123123',
14
rules: [{pattern: /(^1[3|5|8][0-9]{9}$)/, message: '手机号码规则不匹配'}]
15
}
16
}

分析之后我们发现,表单字段的规则,已经被提炼成为变量,通过在表单组件中配置 rules 来对每一个字段设定不同的规则。

同样的道理,每一个规则,都应该有对应的验证函数,该验证函数与 rules 中的字段是一对一的映射关系。因此我们也因为维护一份配置表

code.ts
1
var strategys = {
2
required: function (value, rule) {
3
if (value === '') {
4
return rule.message || '该字段不能为空';
5
}
6
},
7
// 限制最小长度
8
min: function (value, rule) {
9
if (value.length < rule.min) {
10
return rule.message || `该字段最小长度为${rule.min}`;
11
}
12
},
13
// 限制最小长度
14
max: function (value, rule) {
15
if (value.length > rule.max) {
16
return rule.message || `该字段最大长度为${rule.max}`;
17
}
18
},
19
// 手机号码格式
20
pattern: function (value, rule) {
21
if (!rule.pattern.test(value)) {
22
return rule.message || '正则匹配失败';
23
}
24
}
25
};
26

规则作为变量被提炼出来,规则的映射表也有了,那么我们只需要再封装一个验证对象即可。

code.ts
1
function Validator () {
2
// 格式 { username: { value: '张三', rules: [{}, {}] } }
3
this.fields = {};
4
};
5
6
// 添加一个字段
7
Validator.prototype.addField = function (name, value, rules) {
8
this.fields[name] = {
9
value,
10
rules
11
}
12
};
13
14
// 添加多个字段
15
Validator.prototype.addFields = function(fields) {
16
this.fields = fields
17
}
18
19
// 验证,并返回验证结果
20
Validator.prototype.validate = function() {
21
// 通过的字段,与错误的字段
22
const result = { fields: [], errorFields: [] }
23
Object.keys(this.fields).forEach(key => {
24
// 错误验证结果
25
let errorMessage = ''
26
const value = this.fields[key].value
27
const rules = this.fields[key].rules
28
29
for (var i = 0; i < rules.length; i++) {
30
Object.keys(rules[i]).forEach(validateField => {
31
if (strategys[validateField]) {
32
const message = strategys[validateField](value, rules[i])
33
if (message) {
34
errorMessage = message
35
}
36
}
37
})
38
if (errorMessage) {
39
break
40
}
41
}
42
43
if (errorMessage) {
44
result.errorFields.push({field: key, value, message: errorMessage})
45
} else {
46
result.fields.push({field: key, value})
47
}
48
})
49
50
return result
51
};

这样,我们在使用时,就可以通过 validate 方法,得到表单验证的结果

code.ts
1
var validator = new Validator();
2
validator.addFields(fields)
3
const result = validator.validate()
4
console.log(result)
5
6
if (result.errorFields.length > 0) {
7
console.error('字段验证不通过')
8
}

我们可以字段,来验证一下该封装是否合理。

专栏首页
到顶
专栏目录