分支语句优化--策略模式

发表日期:2019-01-25

在很多业务场景中,完成一项任务时,往往可以有多种不同的方式,比如我们很多商品有不同的促销方式,比如满百减10,满200返40,满三减一等等,每一种方式我们称其为一种策略。每种策略事实上都是一种针对处理相同业务的不同算法,每种算法都是相互独立的,是可以相互替代的,所以策略模式就是:

定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的调用者而变化。策略模式是一种对象行为型模式。

进一步了解5

在项目开发中,有很多需求都是使用不同的算法来实现同一种需求,比如查找、排序、表单校验等。这里我以前端更为常见的表单校验来举例。我们常用的方法是硬编码在一个公共类中,如有多种校验行为,可以将对应的校验算法写到工具类中,每一种算法对外提供一个方法以供调用。下面是在我们火车票、酒店H5项目中有关校验的写法,我们将校验方法与其他工具方法放在了工具类中同一级。

当然也可以将这些校验算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的校验方式,需要修改封装算法工具类的源代码;更换查找算法,也需要修改调用端的代码。

其实我们在利用某些动画库写动效时,一定会见到类似如下的写法:

animate({width: '200px'}, 1000, 'linear')

其中,第三个参数可选不同的缓动算法,比如linear、easeout等,算法复杂的处理过程对调用者隐藏,这是一个很典型的策略模式应用。

模式实例

我们希望表单校验的调用也可以如此优雅,下面是一个表单正则校验策略对象。

// 表单正则验证策略对象
var Inputstrategy = function() {
	var strategy = {
		// 是否为空
		notNull: function(value) {
			return /\s+/.test(value) ? '请输入内容' : ''
		},
		// 是否是个数字
		number: function(value) {
			return /^[0-9]+(\.[0-9]+)?$/.test(value) ? '' : '请输入数字'
		},
		// 是否是本地电话
		phone: function(value) {
			return /^\d{3}\-\d{8}$|^\d{4}\-\d{7}$/.test(value) ? '' : '请输入正确的电话号'
		}
	}
	return {
		// 验证接口type算法value表单值
		check: function (type, value) {
			// 去除首尾空白符
			value = value.replace(/^\s+|\s+$/g, '');
			return strategy[type] ? strategy[type](value) : '没有该类型的检测方法'
		},
		// 添加策略
		addStrategy: function (type, fn) {
			strategy[type] = fn
		}
	}
}()

可以看到这个策略类除了导出一个check方法做校验,还导出了一个添加策略的方法addStrategy,这样即可实现策略方法的临时性修改,而不需要修改策略对象内部的代码。

Inputstrategy.ckeck('nickname', 'xxxxxxxx')

如此的调用方式更加清晰可读,算法实现类中也不再臃肿无序。

小结

策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,增加了用户对策略类使用的成本,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。

对于分支语句优化,一共有三种模式涉及。分别为工厂方法模式、状态模式和策略模式。对于工厂方法模式,它是一种创建型模式,他的最终目的是创建对象。而状态模式和策略模式都是行为型模式,在状态模式中,其核心是对状态的控制来决定不同的行为表现,所以状态之间通常是不可替代的,否则将产生不同的行为结果。而策略模式核心是算法,往往每种算法要处理的业务逻辑相同,因此他们可以相互替换,当然策略类也不必关心调用者的环境,因为同一种策略模式最终产出的结果是一定的。

参考文献

最后更新于