策略模式,是一种封装算法与规则的设计模式。
我们在学习封装的终极奥义时已经知道,封装就是提炼出来相同点,将不同点作为变量。
当不同点是一段逻辑、一个算法、一个规则时,我们就可以进一步将这个不同点进行封装。
这就是策略模式解决问题的思路。
公司里每位员工的奖金都不一样。
员工的奖金,由员工基本工资与员工等级相关。
奖金 = 基本工资 * 等级对应的倍数
于是,我们就可以封装一个计算奖金的方法如下
10function getBouns(base, level) {20if (level == 'A') {30return base * 540}50if (level == 'B') {60return base * 470}80if (level == 'C') {90return base * 310}11if (level == 'D') {12return base * 213}14if (level == 'E') {15return base * 116}17}
有了这个方法,计算奖金就很简单
1const p1 = {2name: '张三',3base: 1000,4level: 'A'5}6p1.bouns = getBouns(p1.base, p1.level)78console.log(p1)
这样封装虽然很简单,但是存在潜在的风险:奖金的计算方式,随时可能会改变。
当奖金的计算方式发生了变化,新增了更多的员工等级,我们就不得不修改 getBouns
方法。
因此我们需要对该方法进行进一步的提炼,把该方法中的变量:奖金的计算方式提炼出来。这样,我们就不担心变量发生改变了。
此处,我们需要关注两个地方
因此,我们应该建立一个员工等级与奖金计算方式的隐射关系
10const map = {20A: function(base) {30return base * 540},50B: function (base) {60return base * 470},80C: function (base) {90return base * 310},11D: function (base) {12return base * 213},14E: function (base) {15return base * 116},17}
即使以后有所修改,我们只需要修改该配置文件或者配置表即可。
员工的奖金依据该配置进行计算
1function getBouns(base, level) {2return map[level](base)3}
1const p2 = {2name: '李四',3base: 1200,4level: 'B'5}6p2.bouns = getBouns(p2.base, p2.level)78console.log(p2)
表单验证是一个非常常见的案例,我们也可以使用策略模式来解决规则的验证问题。
借鉴 antdesign 的表单规则,假设我们从组件中,获取得到的字段数据与其对应的规则如下
1020// 从组件中获取到的字段内容,其中包括了具体的值,与传入的校验规则30const fields = {40username: {50value: '张三',60rules: [{required: true, message: '该字段为必填'}, {max: 10}]70},80password: {90value: '123',10rules: [{required: true}, {min: 6}]11},12phone: {13value: '123123',14rules: [{pattern: /(^1[3|5|8][0-9]{9}$)/, message: '手机号码规则不匹配'}]15}16}
分析之后我们发现,表单字段的规则,已经被提炼成为变量,通过在表单组件中配置 rules
来对每一个字段设定不同的规则。
同样的道理,每一个规则,都应该有对应的验证函数,该验证函数与 rules 中的字段是一对一的映射关系。因此我们也因为维护一份配置表
10var strategys = {20required: function (value, rule) {30if (value === '') {40return rule.message || '该字段不能为空';50}60},70// 限制最小长度80min: function (value, rule) {90if (value.length < rule.min) {10return rule.message || `该字段最小长度为${rule.min}`;11}12},13// 限制最小长度14max: function (value, rule) {15if (value.length > rule.max) {16return rule.message || `该字段最大长度为${rule.max}`;17}18},19// 手机号码格式20pattern: function (value, rule) {21if (!rule.pattern.test(value)) {22return rule.message || '正则匹配失败';23}24}25};26
规则作为变量被提炼出来,规则的映射表也有了,那么我们只需要再封装一个验证对象即可。
10function Validator () {20// 格式 { username: { value: '张三', rules: [{}, {}] } }30this.fields = {};40};5060// 添加一个字段70Validator.prototype.addField = function (name, value, rules) {80this.fields[name] = {90value,10rules11}12};1314// 添加多个字段15Validator.prototype.addFields = function(fields) {16this.fields = fields17}1819// 验证,并返回验证结果20Validator.prototype.validate = function() {21// 通过的字段,与错误的字段22const result = { fields: [], errorFields: [] }23Object.keys(this.fields).forEach(key => {24// 错误验证结果25let errorMessage = ''26const value = this.fields[key].value27const rules = this.fields[key].rules2829for (var i = 0; i < rules.length; i++) {30Object.keys(rules[i]).forEach(validateField => {31if (strategys[validateField]) {32const message = strategys[validateField](value, rules[i])33if (message) {34errorMessage = message35}36}37})38if (errorMessage) {39break40}41}4243if (errorMessage) {44result.errorFields.push({field: key, value, message: errorMessage})45} else {46result.fields.push({field: key, value})47}48})4950return result51};
这样,我们在使用时,就可以通过 validate
方法,得到表单验证的结果
1var validator = new Validator();2validator.addFields(fields)3const result = validator.validate()4console.log(result)56if (result.errorFields.length > 0) {7console.error('字段验证不通过')8}
我们可以字段,来验证一下该封装是否合理。