[Golang修仙之路] 策略模式

128 阅读3分钟

1. 什么是「策略模式」?

个人的理解就是, 实现同一件事情, 可能有不同的算法/策略, 通过使用策略模式, 可以让策略的选择更平滑, 添加/删除一个策略更方便(因为符合开闭原则, 扩展性更好).

2. 策略模式的实现

首先要定义一个策略接口, 有若干不同策略的具体实现去实现这个接口. 然后对于策略的使用者, 只需要依赖抽象的策略接口, 然后通过一个SetStrategy()设置策略的方法, 来指明当前抽象接口选择哪个具体实现(这也是多态的体现). 最后调用抽象策略接口定义的具体方法即可.

2.1 具体场景Demo

光这样说肯定不好理解, 下面我来举1个例子, 这个例子的场景是我自己想的, 不是抄的.

篮球场上有多种不同的防守战术, 我们认为每一种战术是一个「策略」.

首先定义防守策略接口:

package interfaces

type Defend interface {
	Defend()
}

然后写几个具体实现, 意思一下hhh

// strategy 1
type Focus struct{}

func (f *Focus) Defend() {
	fmt.Println("执行盯人包夹防守战术")
}

// strategy 2
type OneOnOne struct{}

func (o *OneOnOne) Defend() {
	fmt.Println("执行一对一防守战术")
}

// strategy 3
type TwoThree struct{}

func (t *TwoThree) Defend() {
	fmt.Println("执行二三联防战术")
}

然后通过一个很多文章上叫Context的类, 去承担 设置策略 + 执行策略 的工作. 我们这里搞了一个 Coach教练类, 来设置防守战术 并 敦促队员执行.

可以看到, Coach类内部确实只依赖接口, Coach对策略的增加/减少是无感的, 扩展性好.

type Coach struct {
	name string
	defendStrategy interfaces.Defend
}

func (c *Coach) SetDefendStrategy(strategy interfaces.Defend) {
	fmt.Printf("%s 切换防守策略\n", c.name)
	c.defendStrategy = strategy
}

func (c *Coach) ExecuteDefend() {
	c.defendStrategy.Defend()
}

func NewCoach(name string) *Coach {
	return &Coach{
		name: name,
	}
}

最后我们执行main函数, 并看一下效果:

func main() {
    coach := NewCoach("波波维奇")
    coach.SetDefendStrategy(&strategies.Focus{})
    coach.ExecuteDefend()

    coach.SetDefendStrategy(&strategies.OneOnOne{})
    coach.ExecuteDefend()
}

image.png

程序运行成功, 但是我们注意到, 在main函数中, 我们确实要创建策略的具体实现的实例来作为参数传入, 这一点也被归为策略模式的缺点之一. 但是我认为, 不这么写还能怎么写呢, 策略模式总比直接依赖具体实现的方式要强得多.

3. 策略模式的优缺点

3.1 优点

  1. 符合开闭原则, 扩展性好, 因此在多人合作的时候, 代码易于维护(增加策略的时候, 只需要关注策略的具体实现)
  2. 可以在运行时切换策略.
  3. 可以将策略的具体实现和策略的使用这两部分代码隔离开来.

3.2 缺点

  1. 每种策略都对外暴露. (如果这也算缺点的话)

4. 参考

  1. 《设计模式》: refactoringguru.cn/design-patt…
  2. 刘丹冰, Easy设计模式: www.bilibili.com/video/BV1Eg…

5. 题外话

一点思考, 我之前学习的时候, 总是有一个地方不懂就钻牛角尖总想弄懂, 可是有些问题弄懂了面试也不问, 过了一段时间也忘记了, 对写更优雅的代码也没有帮助. 那么不如松弛一点, 尽力弄懂当前阶段能弄懂的即可, 一本武林秘籍, 给不同修为的人看, 他们能领悟的东西也是不同的吗.

杜绝“一个知识点只看一次, 一次看透”这个思想, 小猪乔治跟自己说.