设计模式-策略模式(会飞的鸭子)

策略模式是一枚经典的设计模式,也非常实用。我们就用《Head First 设计模式》一书中的鸭子游戏的案例,来认识它吧。

先从简单的模拟鸭子应用说起

Joe上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。游戏中会出现各种鸭子,一边游泳一边呱呱叫。作为一个OO(面向对象)的程序员,Joe在设计游戏的时候使用了相当标准的OO技术。设计了一个鸭子父类,并让各种鸭子集成这个超类。如下图所示:

然而互联网公司的节奏超快,上线一个月后,公司觉得需要在产品上加入真正创新的东西,来适应激烈的市场竞争。经过一周的头脑风暴,公司的董事会决定在下一次孤独会议上演示一些“真正”让人印象深刻的东西来振奋人心。

现在我们得让鸭子能飞

主管们确定,此模拟游戏需要会飞的鸭子来讲竞争者泡在后头,当然,在这个时候,Joe拍拍胸脯告诉主管们,他只要一个星期就可以搞定,毕竟Joe是一个OO的程序,这有何难。

Joe: 我只要在Duck类上加上fly()方法,然后所有的鸭子都会继承fly()方法。这是我大显身手展示OO的优越的时候了。

但是可怕的问题发生了...

Boss: Joe,我正在股东大会上,刚刚看了一下展示,有很多“橡皮小黄鸭”在屏幕上飞来飞去。你这是在逗我嘛,你可能要开始去逛逛拉勾网了啊。 Joe: 好吧,我好想设计上确实有一点小失误,老大请让我再想想。。。

Joe忽略了一件事,并非Duck的所哟䢮都会飞。joe在Duck超类上加上新的行为,会使得默写不合适该行为的子类也具有该行为。现在可好了SimDuck这个游戏中有了一个无生命的会飞的东西。

Joe 想到了继承

可以把橡皮鸭类中的fly()方法覆盖掉。就好像覆盖quack()的做法一样。因为橡皮鸭子不会呱呱叫,所以quack的定义覆盖成吱吱叫

Joe:可是,如果以后我加入木头鸭(DecoyDuck)又会如何。木头呀不会飞也不会叫。 Joe认识到继承可能不是答案,因为他刚刚得知,接下来的几个月,游戏会有大的更新,至于怎么更新还没确定。鸭子的种类会越来越多。每当有新的鸭子子类出现,他就要被迫检查并可能需要覆盖fly()和quark()方法,看对不对,防止再闹笑话。

利用接口如何?

Joe: 我可以吧fly()从超类中取出来,拉进一个flyable接口中。这么一来只有会飞的鸭子才实现此接口。同样的方式,也可以用来设计一个Quackable接口,因为不是所有的鸭子都会叫。不叫的鸭子我们就不继承这个接口呢。 Boss: 这真是一个超笨的注意,你没发现这么一来重复的代码会变多嘛?好多鸭子的Quack和fly的形式都一样,你都要在子类中重复实现它一次吗?万一48个鸭子的之类要稍微修改一下quack或者fly的方法,你难道每个鸭子都去之类改一遍嘛?

揭晓答案吧

这里我们用到的一个设计原则:

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

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

这样做以后,flyBehavior以及quackBehavior就和鸭子类分离出来了。既可以重用这部分代码,也可以新增新的behavior也不会影响到原有的代码。 这里有一个原则:

针对接口编程,不针对实现编程 即,我们不关心flybehavior的实现,只关心它有flyBehavior以及quackBehavior。具体的实现用到了再说这样。

请我喝杯咖啡吧!