策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
面向对象的策略模式
在上一篇提到过一种抽离执行和具体实现的例子就是一个典型的面向对象思想实现的策略模式,各种登录接口的类可以称为一组 策略类 ,而调用他的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>
|
通过策略模式将代码分为策略类和环境类,并进行一些优化。
- 通过策略类避免繁琐的if else语句
- 通过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(':'); this.cache.push(function () { var strategy = ary.shift(); ary.unshift(dom.value); ary.push(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.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(); if (errorMsg) { alert(errorMsg); return false; } };
|
小结
策略模式的优点:
- 利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 提供了对开放封闭原则的完美支持,将算法封装在策略类中,使得它们易于切换,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复代码。
- 在策略模式中利用组合和委托来让Context拥有执行算法的能力(如上述代码中的 Validator构造函数 ),这也是继承的一种更轻便的替代方案。
策略模式的缺点:
- 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context 中要好。
- 要使用策略模式,必须了解所有的策略,必须了解各个策略之间的不同点, 这样才能选择一个合适的策略。