策略模式
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。
struct Order{}
order Order = 订单信息
if payType == 微信支付{
微信支付流程
} else if payType == 支付宝{
支付宝支付流程
} else if payType == 银行卡{
银行卡支付流程
} else {
暂不支持的支付方式
}
如上代码,虽然写起来简单,但违反了面向对象的 2 个基本原则:
单一职责原则
:一个类只有1个发生变化的原因 之后修改任何逻辑,当前方法都会被修改开闭原则
:对扩展开放,对修改关闭 当我们需要增加、减少某种支付方式(积分支付/组合支付),或者增加优惠券等功能时,不可避免的要修改该段代码
特别是当 if-else
块中的代码量比较大时,后续的扩展和维护会变得非常复杂且容易出错。在阿里《Java开发手册》中,有这样的规则:超过3层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现
。
策略模式是解决过多 if-else
(或者 switch-case
) 代码块的方法之一,提高代码的可维护性、可扩展性和可读性。
定义
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
理解
策略,也就是计策的意思。刘备每到关键时刻就可以打开一个(封装的)锦囊,得到一个战胜敌人的策略(火攻、水攻等等);再例如我们想去黄山旅游,我们可以选择不同的策略(飞机、火车、开车),不管是使用哪种策略,不会影响我们抵达黄山游览,只是耗时长短的问题。
代码
这里我们用最容易理解的计算器为例。计算器支持加、减、乘、除等等计算方法(策略)。只要我们输入两数字,使用其中一个策略即可得到一个结果。对于计算器使用者来说,无需关心实际的算法运算过程,只需要输入数字即可。而对于算法的实现者来说,新的算法策略只要能接受两个参数入参进行运算,即可对接到计算器中,扩展了计算器的功能。
这里我们先定义一个通用的计算策略Calc
,接收两个参数a
和b
type Strategy interface{
Calc(a,b int) int
}
加法策略
//AddStrategy 加法策略
type AddStrategy struct {
}
func (t AddStrategy) Calc(a, b int) int {
return a + b
}
实现减法策略
//SubStrategy 减法策略
type SubStrategy struct {
}
func (t SubStrategy) Calc(a, b int) int {
return a - b
}
将策略对接到实际的计算器中
//Calculator 计算器
type Calculator struct {
s Strategy
}
func (t *Calculator) setStrategy(s Strategy) {
t.s = s
}
func (t Calculator) GetResult(a, b int) int {
return t.s.Calc(a, b)
}
对于计算器来说,他只需要使用setStrategy
设置不同的计算策略,通过GetResult
函数获取结果。
开始使用计算器计算
func main() {
add := AddStrategy{} //加法策略
sub := SubStrategy{} //减法策略
//计算器通过setStrategy设置不同策略,解耦了计算器和算法实现类
cal := &Calculator{}
cal.setStrategy(add)
fmt.Println("加法策略结果:", cal.GetResult(1, 1))
cal.setStrategy(sub)
fmt.Println("减法策略结果:", cal.GetResult(1, 1))
}
输出结果
加法策略结果: 2
减法策略结果: 0
总结
策略模式的重点在于策略的设定,以及普通类Calculator
与策略Strategy
的对接。通过更换实现同一个接口Strategy
的不同策略类AddStrategy
和SubStrategy
。降低了Calculator
的维护成本,解耦和计算器和算法实现,符合设计模式的开放闭合原则。
完整代码如下
package main
import "fmt"
//这里以计算器为例,包含两种功能 (加法、减法)
/*
//这种代码优点,如果只有这两种计算方法,编写简单容易理解。如果后续计算器包含很多计算方法(乘法、除法) Cal 结构体就需要不同的修改。违背了代码放开闭合原则。好的代码,应该是支持扩展,减少对原有代码修改,可以快速满足不同用户的需求。
type Cal struct{
}
func (t Cal)Add(a,b int) int{
return a+b
}
func (t Cal)Sub(a,b int)int{
return a-b
}
*/
//Strategy 定义策略接口
type Strategy interface {
Calc(a, b int) int
}
//AddStrategy 加法策略
type AddStrategy struct {
}
func (t AddStrategy) Calc(a, b int) int {
return a + b
}
//SubStrategy 减法策略
type SubStrategy struct {
}
func (t SubStrategy) Calc(a, b int) int {
return a - b
}
//Calculator 计算器
type Calculator struct {
s Strategy
}
func (t *Calculator) setStrategy(s Strategy) {
t.s = s
}
func (t Calculator) GetResult(a, b int) int {
return t.s.Calc(a, b)
}
func main() {
add := AddStrategy{}
sub := SubStrategy{}
//计算器通过setStrategy设置不同策略,解耦了计算器和算法实现类
cal := &Calculator{}
cal.setStrategy(add)
fmt.Println("加法策略结果:", cal.GetResult(1, 1))
cal.setStrategy(sub)
fmt.Println("减法策略结果:", cal.GetResult(1, 1))
}