策略模式【Go实现】

Go实现设计模式——策略模式

策略模式

在生活中,我们经常会处于需要进行某种策略选择的场景——例如,购物节有不同的打折方案供我们选择、出行旅游也有多种交通方式等。一种策略即是一种“方案”,同一类策略都具有相同类型的输入和输出。在程序设计过程中,我们也会遇到需要进行策略选择的情况,比如棋盘游戏算法设计中,针对不同的难度选用不同类型的算法等。那么如何来实现策略的灵活变更呢?
策略模式通过定义算法族的方式,分别封装不同的算法,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

模拟鸭子应用(DuckSimulation)

初始项目

我们来实现一个鸭子模拟的应用,鸭子会游泳、会呱呱叫。依据面向对象的设计方法,我们暂时设计出了下面的产品。

Duck Init

@startuml Initial Duck Class Diagram
abstract class Duck{
    +Swin()
    +Quack()
    {abstract} +Display()
}
class MallarDuck extends Duck{
    +Display()
}
class RedHeadDuck extends Duck{
    +Display()
}
@enduml

新的需求——让鸭子飞起来

现在我们需要给鸭子们添加新的功能,让鸭子飞起来!
最简单的方法便是给父类Duck添加Fly方法。

在这里插入图片描述

@startuml Duck Class With Fly
abstract class Duck{
+Swin()
+Quack()
+Fly()
{abstract} +Display()
}
class MallarDuck extends Duck{
+Display()
}
class RedHeadDuck extends Duck{
+Display()
}
@enduml
很好,至少目前看来我们实现了这个需求,而且只增加了少量的代码,同时我们没有额外修改已经存在的代码,干得漂亮!

我们遇到了新的问题——一只会飞的橡皮鸭

我们又遇到了问题——当我们增加了一只橡皮鸭(RubberDuck)的时候,它飞了起来!我们可不能让橡皮鸭飞起来。

在这里插入图片描述

@startuml Rubber Duck Can Fly
abstract class Duck{
    +Swin()
    +Quack()
    +Fly()
    {abstract} +Display()
}
class MallarDuck extends Duck{
    +Display() 
}
class RedHeadDuck extends Duck{
    +Display()
}
class RubberDuck extends Duck{
    +Quack()
    +Display()
}
@enduml
aRubberDuck := &RubberDuck{}
aRubberDuck.Fly() // it works!
解决方案一

利用继承的特性,我们尝试让橡皮鸭重写(overrideDuckFly方法,来让它老老实实呆在地上。
在这里插入图片描述

@startuml Rubber Duck Cannot Fly
abstract class Duck{
    +Swin()
    +Quack()
    +Fly()
    {abstract} +Display()
}
class MallarDuck extends Duck{
    +Display() 
}
class RedHeadDuck extends Duck{
    +Display()
}
class RubberDuck extends Duck{
    +Quack()
    +Display()
    +Fly()
}
@enduml

橡皮鸭总算是不会自己飞起来了!
可是仔细想想,如果我们有新的不会飞的小鸭子们,大家都要重写一遍Fly方法吗?会飞的姿势五花八门,不会飞的灵魂千篇一律。明明大家都是一样的不会飞!这样重复的代码可太多了。况且调用一个“没有用”的方法可真让人摸不着头脑。

解决方案二

利用接口,让需要飞的鸭子们各自实现自己的飞行姿势!

在这里插入图片描述

@startuml Ducks With Fly Interface
interface Flyable{
    Fly()
}
abstract class Duck{
    +Swin()
    +Quack()
    {abstract} +Display()
}
class MallarDuck extends Duck implements Flyable {
    +Display()
}
class RedHeadDuck extends Duck implements Flyable{
    +Display()
}
class RubberDuck extends Duck{
    +Quack()
    +Display()
}
@enduml

Oh! 我们避免了重写大量Fly方法——现在只需要大量的实现……

给我们的小鸭子装上飞行插件

两种解决方案都不太完美。我的意思是,既然有很多类不需要Fly,同时,剩下的类里面,有的需要同样形式的Fly,有的又需要不同形式的Fly,那我们何不把Fly抽象出来呢?不是让它作为一个低级的方法,而是把它变成一个emmm插件?这样我就可以方便定制类的行为了!不是吗?

在这里插入图片描述

@startuml Duck With Fly Strategy
interface FlyBehavior{
    Fly()
}
abstract class Duck{
    -FlyBehavior doFly
    +Swin()
    +Quack()
    +Fly()
    +setFlyBehavior(Flyable)
    {abstract} +Display()
}
Duck::doFly -l-> FlyBehavior
class MallarDuck extends Duck {
    +Display()
}
class RedHeadDuck extends Duck {
    +Display()
}
class RubberDuck extends Duck {
    +Quack()
    +Display()
}
class NoFly implements FlyBehavior{
    +Fly()
}
class FlyWithWings implements FlyBehavior{
    +Fly()
}
@enduml

这样我们就可以方便定制我们的飞行方式了!而且不需要为了飞行方式的变动而修改小鸭子们了!换言之,我们将鸭子的飞行行为的具体实现托管给了FlyBehavior,我们成功的分开的会变化和不会变化的部分。现在我们甚至可以在程序运行过程中对鸭子们的飞行表现进行配置!

type FlyBehavior interface {
	Fly()
}
// NoFly 无需飞行
type NoFly struct {
}

func (f *NoFly) Fly() {
	fmt.Println("不用飞~")
}


type Duck struct {
	doFly FlyBehavior
}
func (duck *Duck) Fly() {
	// 将飞行行为托管给飞行策略
	duck.doFly.Fly()
}

func (duck *Duck) SetFlyBehavior(flyBehavior FlyBehavior) {
	duck.doFly = flyBehavior
}
aMallarDuck := ducksim.MallarDuck{}
aMallarDuck.SetFlyBehavior(&ducksim.FlyWithWings{})
aMallarDuck.Swim()
aMallarDuck.Fly()

aRubberDuck := ducksim.RubberDuck{}
aRubberDuck.SetFlyBehavior(&ducksim.NoFly{})
aRubberDuck.Quack()
aRubberDuck.Fly()

程序输出(go build main.go)

Duck Swiming
用翅膀飞!
吱吱
不用飞~

总结

通过策略模式,我们将飞行这一行为从方法提升到了类。将飞行的具体行为托管给了FlyBehavior接口,这样,我们就可以专心控制鸭子和飞行这两件事了。结合策略模式的定义——通过定义算法族的方式,分别封装不同的算法,让它们之间可以互相替换。在这里,FlyBehavior便是我们定义的算法族。
策略模式很好的体现了“面向接口编程”,避免类和一个具体实现高度耦合。同时也符合多用组合少用继承的设计原则。
源码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值