定义
定义了算法(一些列实现不同功能的方法)家族,分别封装起来,让它们之间相互可以替换,此模式让算法的变化,不会影响到使用算法的客户。
策略模式结构图
解释
Strategy:策略类,定义所有的算法子类支持的算法的公共接口。
Context:上下文类,维护一个策略Strategy对象的引用,在上下文类中,用一个方法来调用不同的算法类的方法。
ConcreteStrategy:封装了具体的算法或行为的类,都继承了Strategy类。
案例
做一个商场收银软件,根据客户购买的商品以及商场不同的营销模式,比如说满500减20,商品打八折等,向客户收费。
版本1.0
type Product struct {
Num int64
Price float64
Discount float64
}
func Pay(product *Product, str string) (res float64) {
switch str {
case "原价":
return float64(product.Num) * product.Price * product.Discount
case "打八折":
return float64(product.Num) * product.Price * product.Discount
case "打七折":
return float64(product.Num) * product.Price * product.Discount
case "满200减10块":
total := float64(product.Num) * product.Price * product.Discount
if total >= 200 {
return total - 20
}
return total
default:
return float64(product.Num) * product.Price * product.Discount
}
}
func main() {
product := &Product{
Num: 20,
Price: 3,
Discount: 0.8,
}
fmt.Println(Pay(product, "打八折"))
}
缺点:
1.代码重复比价多
2.不符合单一职责原则,所有的计算商品的总价都放在一个函数内实现
3.违反了开闭原则 :对扩展是开放的,对修改是封闭的,即增加新的功能是不要修改原来的模块和源代码。
版本2.0
1、定义策略类
// Strategy 1.定义策略类,并且定义公共算法接口
type Strategy interface {
AlgorithmInterface()
}
2、定义封装了具体的算法或方法的类,实现Strategy接口
func (concreteStrategyA *ConcreteStrategyA) AlgorithmInterface() {
fmt.Println("实现算法a")
}
type ConcreteStrategyB struct {
}
func (concreteStrategyB *ConcreteStrategyB) AlgorithmInterface() {
fmt.Println("实现算法b")
}
type ConcreteStrategyC struct {
}
func (concreteStrategyC *ConcreteStrategyC) AlgorithmInterface() {
fmt.Println("实现算法c")
}
3、定义context类,用一个concreteStrategy来配置,维护strategy对象的引用
func main() {
ctx := new(Contexts)
// 由于实例化的是不同的策略对象,所以最终调用公共方法是获得结果是不同的
ctx = ctx.NewContexts(new(ConcreteStrategyA))
ctx.ContextInterface()
ctx = ctx.NewContexts(new(ConcreteStrategyB))
ctx.ContextInterface()
ctx = ctx.NewContexts(new(ConcreteStrategyC))
ctx.ContextInterface()
}
4、测试
func main() {
ctx := new(Contexts)
ctx.strategy = new(ConcreteStrategyA)
ctx.ContextInterface()
ctx.strategy = new(ConcreteStrategyB)
ctx.ContextInterface()
ctx.strategy = new(ConcreteStrategyC)
ctx.ContextInterface()
}
版本3.0 :使用简单工厂+策略模式解决策略对象有客户端创建的弊端
import "fmt"
func main() {
ctx := new(Contexts)
ct := ctx.NewContexts("a")
ct.ContextInterface()
}
// Strategy 1.定义策略类,并且定义公共算法接口
type Strategy interface {
AlgorithmInterface()
}
type ConcreteStrategyA struct {
}
func (concreteStrategyA *ConcreteStrategyA) AlgorithmInterface() {
fmt.Println("实现算法a")
}
type ConcreteStrategyB struct {
}
func (concreteStrategyB *ConcreteStrategyB) AlgorithmInterface() {
fmt.Println("实现算法b")
}
type ConcreteStrategyC struct {
}
func (concreteStrategyC *ConcreteStrategyC) AlgorithmInterface() {
fmt.Println("实现算法c")
}
type Contexts struct {
strategy Strategy // 类的嵌入
}
// NewContexts 类似于Java中的构造函数
func (ctx *Contexts) NewContexts(typ string) *Contexts {
contexts := new(Contexts)
switch typ {
case "a":
concreteStrategyA := new(ConcreteStrategyA)
contexts.strategy = concreteStrategyA
return contexts
case "b":
concreteStrategyB := new(ConcreteStrategyB)
contexts.strategy = concreteStrategyB
return contexts
case "c":
concreteStrategyC := new(ConcreteStrategyC)
contexts.strategy = concreteStrategyC
return contexts
default:
return nil
}
}
func (ctx *Contexts) ContextInterface() {
ctx.strategy.AlgorithmInterface()
}
优点:如果仅仅使用工厂模式,客户端需要理解Contexts类和各个策略类,但是如果使用简单工厂+策略模式,客户端仅仅只需理解Contexts类即可,类的创建也封装到了Contexts的构造方法中了。
使用场景
1.策略模式体现了开闭原则:策略模式把一系列的可变算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改
2.策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略实现都是兄弟关系,实现了同一个接口或者继承了同一个抽象类。这样只要使用策略的客户端保持面向抽象编程,就可以动态的切换不同的策略实现以进行替换。
优缺点
优点
1.就是把具体的算法实现从业务逻辑中剥离出来,成为一系列独立算法类,使得它们可以相互替换。
2.不是如何来实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展
3.策略模式就是把各个平等的具体实现进行抽象、封装成为独立的算法类,然后通过上下文和具体的算法类来进行交互。各个策略算法都是平等的,地位是一样的,正是由于各个算法的平等性,所以它们才是可以相互替换的。虽然我们可以动态的切换各个策略,但是同一时刻只能使用一个策略。
缺点
1..客户端必须了解所有的策略,清楚它们的不同,然后使用不同的策略,即把策略对象的创建又交给了客户端
2、增加了对象的数量,由于每一个策略都是一个策略类。
3、只适合同一个客户端同一时刻只使用一个策略。这些策略是平等的。
总结
1.策略和上下文的关系:在策略模式中,一般情况下都是上下文持有策略的引用,以进行对具体策略的调用。