go 设计模式——策略模式

语言: CN / TW / HK

策略模式

问题引入

当我们有一堆鸭子,各个鸭子都有相同的游泳(swim),自我描述(display)方法,因为它们都会游泳和自我描述;但是不同的鸭子又有自己特有的飞行(fly),叫声(quack),这时如何使用设计模式来解决这样的问题呢?

首先会想到的方法是,有一个鸭子超类,swim和diplay作为公有的方法,不同鸭子的子类去覆盖实现各自的fly和quack方法,貌似就可以解决这个问题了

不过,每当新的鸭子类型出现,就需要重新去实现fly和quack方法;甚至如果多个鸭子类型拥有相同的fly或quack方法,代码就不能很好地复用了。

在这里,鸭子的行为在子类里不断地改变,并且让所有子类都拥有这些行为是不恰当的。继承并不能很好地解决问题

设计原则

设计模式中的一个设计原则是: 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定地代码有所区分。

这个原则的另一个思考方式是,把会变的部分取出来并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。

这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法 让系统中的某部分改变不会影响其他部分

重新设计

是时候把鸭子的行为抽取出来了

  • 分开变化和不变化的部分

    • 变化的部分:quack,fly
    • 不变化的部分:swim display

因此,将quack和fly行为抽取出来:

type basicDuck interface {
    quackBehaviour
    flyBehaviour
    display()
    swim()
}

type duck struct {
    quackBehaviour
    flyBehaviour
    name string
}

func (d *duck) display() {
    fmt.Printf("I am %+v\n", d.name)
}

func (d *duck) swim() {
    fmt.Printf("I am %+v, I can swim\n", d.name)
}

可以看到,quack和fly作为两个接口被抽离了出来,而display和swim仍然作为鸭子固定不变的部分

看下quack和fly的部分

type quackBehaviour interface {
    quack()
}
type flyBehaviour interface {
    fly()
}

我们利用接口代表每个行为,比方说quackBehaviour和flyBehaviour,而行为的每个实现都将实现其中的一个接口;实际的实现就不会被绑死在鸭子的子类中了,即具体的行为编写在实现了quackBehaviour和flyBehaviour的子类中;

这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子无关了;而我们可以新增一些行为,不会影响到既有的行为类,也不会影响到现有的鸭子子类。

现在实现了以下几个飞行行为类:

type flyWithWings struct {
}

func (f *flyWithWings) fly() {
    fmt.Println("fly with wings!")
}

type cantFly struct {
}

func (f *cantFly) fly() {
    fmt.Println("cant fly...")
}

type flyWithRocketPower struct {
}

func (f *flyWithRocketPower) fly() {
    fmt.Println("fly with rocket power!")
}

以及以下几个呱呱叫行为类:

type quackWithGuaGua struct {
}

func (q *quackWithGuaGua) quack() {
    fmt.Println("quack with guagua!")
}

type quackWithGuGu struct {
}

func (q *quackWithGuGu) quack() {
    fmt.Println("quack with gugu!")
}

type quackWithEngine struct {
}

func (q *quackWithEngine) quack() {
    fmt.Println("quack with engine!")
}

这时,是时候整合鸭子的行为了。例如我们有一个火箭动力鸭,该如何实现这个鸭子呢?

type rocketDuck struct {
    *duck
}

func main() {
    d := rocketDuck{
        &duck{
            &quackWithEngine{},
            &flyWithRocketPower{},
            "rocket duck",
        },
    }
    doAction(d)
}

func doAction(d basicDuck) {
    d.display()
    d.swim()
    d.quack()
    d.fly()
}

运行之,看效果:

I am rocket duck
I am rocket duck, I can swim
quack with engine!
fly with rocket power!

总结

这就是策略模式,即:定义了算法族,分别封装起来,让它们之前可以相互替换,此模式让算法的变化独立于使用算法的客户

参考文章

《head first设计模式》

分享到: