14

go 设计模式——策略模式

 4 years ago
source link: https://studygolang.com/articles/27694
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

策略模式

问题引入

当我们有一堆鸭子,各个鸭子都有相同的游泳(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设计模式》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK