在生活中,我们经常会处于需要进行某种策略选择的场景——例如,购物节有不同的打折方案供我们选择、出行旅游也有多种交通方式等。一种策略即是一种“方案”,同一类策略都具有相同类型的输入和输出。在程序设计过程中,我们也会遇到需要进行策略选择的情况,比如棋盘游戏算法设计中,针对不同的难度选用不同类型的算法等。那么如何来实现策略的灵活变更呢? 策略模式通过定义算法族的方式,分别封装不同的算法,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
我们来实现一个鸭子模拟的应用,鸭子会游泳、会呱呱叫。依据面向对象的设计方法,我们暂时设计出了下面的产品。
@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!
利用继承的特性,我们尝试让橡皮鸭重写(override
)Duck
的Fly
方法,来让它老老实实呆在地上。
@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 Interfaceinterface 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 Strategyinterface 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
便是我们定义的算法族。
策略模式很好的体现了“面向接口编程”,避免类和一个具体实现高度耦合。同时也符合多用组合少用继承的设计原则。