五、策略模式(Strategy Pattern)
1.概念介绍
策略模式(Strategy Pattern):封装一系列算法,支持我们在运行时,使用相同接口,选择不同算法。它的目的是为了将算法的使用与算法的实现分离开来。
策略模式通常会有两部分组成,一部分是策略类,它负责实现通用的算法,另一部分是环境类,它用户接收客户端请求并委托给策略类。
2.优缺点
2.1优点
- 有效地避免多重条件选择语句;
- 支持开闭原则,将算法独立封装,使得更加便于切换、理解和扩展;
- 更加便于代码复用;
2.2缺点
- 策略类会增多;
- 所有策略类都需要对外暴露;
3.基本案例
我们可以很简单的将策略和算法直接做映射:
1. let add = {
2. "add3" : (num) => num + 3,
3. "add5" : (num) => num + 5,
4. "add10": (num) => num + 10,
5. }
6. let demo = (type, num) => add[type](num);
7. console.log(demo('add3', 10)); // 13
8. console.log(demo('add10', 12)); // 22
然后我们再把每个策略的算法抽出来:
4.表单验证案例
1. let fun3 = (num) => num + 3;
2. let fun5 = (num) => num + 5;
3. let fun10 = (num) => num + 10;
4. let add = {
5. "add3" : (num) => fun3(num),
6. "add5" : (num) => fun5(num),
7. "add10": (num) => fun10(num),
8. }
9. let demo = (type, num) => add[type](num);
10. console.log(demo('add3', 10)); // 13
11. console.log(demo('add10', 12)); // 22
我们需要使用策略模式,实现一个处理表单验证的方法,无论表单的具体类型是什么都会调用验证方法。我们需要让验证器能选择最佳的策略来处理任务,并将具体的验证数据委托给适当算法。
我们假设需要验证下面的表单数据的有效性:
1. let data = {
2. name : 'pingan',
3. age : 'unknown',
4. nickname: 'leo',
5. }
这里需要先配置验证器,对表单数据中不同的数据使用不同的算法:
1. validator.config = {
2. name : 'isNonEmpty',
3. age : 'isNumber',
4. nickname: 'isAlphaNum',
5. }
并且我们需要将验证的错误信息打印到控制台:
1. validator.validate(data);
2. if(validator.hasErrors()){
3. console.log(validator.msg.join('\n'));
4. }
接下来我们才要实现 validator
中具体的验证算法,他们都有一个相同接口 validator.types
,提供 validate()
方法和 instructions
帮助信息:
1. // 非空值检查
2. validator.types.isNonEmpty = {
3. validate: function(value){
4. return value !== '';
5. }
6. instructions: '该值不能为空'
7. }
2.
3. // 数值类型检查
4. validator.types.isNumber = {
5. validate: function(value){
6. return !isNaN(value);
7. }
8. instructions: '该值只能是数字'
9. }
10.
11. // 检查是否只包含数字和字母
12. validator.types.isAlphaNum = {
13. validate: function(value){
14. return !/[^a-z0-9]/i.test(value);
15. }
16. instructions: '该值只能包含数字和字母,且不包含特殊字符'
17. }
最后就是要实现最核心的 validator
对象:
总结这个案例,我们可以看出 validator
对象是通用的,需要增强 validator
对象的方法只需添加更多的类型检查,后续针对每个新的用例,只需配置验证器和运行 validator()
方法就可以。
1. let validator = {
2. types: {}, // 所有可用的检查
3. msg:[], // 当前验证的错误信息
4. config:{}, // 验证配置
5. validate: function(data){ // 接口方法
6. let type, checker, result;
7. this.msg = []; // 清空错误信息
8. for(let k in data){
9. if(data.hasOwnProperty(k)){
10. type = this.config[k];
11. checker = this.types[type];
12. if(!type) continue; // 不存在类型 则 不需要验证
13. if(!checker){
14. throw {
15. name: '验证失败',
16. msg: `不能验证类型:${type}`
17. }
18. }
19. result = checker.validate(data[k]);
20. if(!result){
21. this.msg.push(`无效的值:${k},${checker.instructions}`);
22. }
23. }
24. }
25. return this.hasErrors();
26. }
27. hasErrors: function(){
28. return this.msg.length != 0;
29. }
30. }
5.小结
日常开发的时候,还是需要根据实际情况来选择设计模式,而不能为了设计模式而去设计模式。通过上面的学习,我们使用策略模式来避免多重条件判断,并且通过开闭原则来封装方法。我们应该多在开发中,逐渐积累自己的开发工具库,便于以后使用。
参考资料
- 《JavaScript Patterns》