本文大幅度参考Head first设计模式中第一章.
先来看一个简单的模拟鸭子应用做起,某公司做了一套模拟鸭子游戏:SimUDuck.游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫.此系统的内部设计使用了标准OOP思想,设计了一个鸭子超类(Superclass),并让各种鸭子继承此超类.类图如下:
为了提升产品的竞争力,高层决策,此模拟程序需要会飞的鸭子.作为一个面向对象的程序员,解决这个问题似乎很easy.只需要在Duck类中添加fly().
问题真的是这样吗?想想如果我们在Duck类中增加fly(),也就表示所有Duck的子类都会飞.而Duck的一个子类[橡皮鸭子并不需要飞的行为].当然,我们覆盖橡皮鸭子的fly()为空实现.如果以后还会增加其他不会飞也不会叫的鸭子呢?
此时,我们应该认真思考下继承所带来的缺点:在父类中增加的非私有属性和方法会强制加给子类,给子类带来了不想要的改变,对父类的改变可能会牵一发而动全身,对以后的扩展相当麻烦.那么,利用接口又如何呢?看下面的类图:
接口可以解决部分问题,但是如果Duck有多个子类都具备相同的fly行为,那么岂不是要将一个fly方法写多次?
我们知道,并非所有的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式.虽然Flyable和Quackable可以解决某一些子类的问题(不会再有会飞的橡皮鸭),但是确造成了代码无法复用,这算是从一个恶梦跳进另外一个恶梦.甚至,在会飞的鸭子中,飞行的动作可能还有多种变化.
现在我们殷切希望有一种建立软件的方法,让我们需要改变软件时,可以在对既有的代码影响最小的情况下,轻易达到花较少时间重做代码,而多让程序去做更酷的事情该有多好...
软件开发一个不变的真理:change.不管当初软件设计得多好,一阵子以后,总是需要成长与改变,否则软件就会[死亡].
现在我们知道使用继承有一些缺失,因为改变鸭子的行为会影响所有鸭子的子类,而这并不恰当.Flyable与Quackable接口一开始似乎还挺不错哦,解决了问题(只有会飞的鸭子继承Flyable),但是Java接口并不能实现代码,所以继承接口无法达到代码的复用.这意味着:无论何时你需要修改某个行为,你必须往下追踪并修改每一个定义此行为的类,一不小心,可能造成新的错误.幸运的是,有一个设计原则,适用于这种状况:
设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不要变化的代码混在一起.专业点的说法:封装变化的部分,好让其他部分不会受到影响.
当代码变化之后,出其不意的部分变得很少,系统变得更有弹性。
换句话说,如果每次新的需求,都会变化到某方面的代码,那么就可以确定这部分代码需要被抽出来,和其他不变动的代码隔离.这样的概念很简单,几乎是每个设计模式背后的精神所在.所有模式都提供一套方法让[系统中某部分改变不会影响其他部分].
现在我们把鸭子的行为从Duck类中取出来!就目前所知除了fly()和quack()的问题之外,Duck类还算一切正常,似乎没有特别需要经常变化和修改的地方.所以除了某些小改变之外,我们不打算对Duck类做太多处理.现在,为了抽出变化的部分,我们建立两组类(完全远离Duck类),一个是fly相关的,一个是quack相关的,每一组类将各自实现各自的动作,比方说,我们可能有一个类实现呱呱叫,一个类实现吱吱叫,一个类实现安静.
我们希望一切能有弹性,毕竟,正是因为一开始的鸭子行为没有弹性,才发生现在的问题.我们还想能够指定行为到鸭子的实例,比如说,想要产生绿头鸭.并指定特定的飞行类型给它.也就是让鸭子的行为可以动态变化.换句话说,我们应该在鸭子类中包含设定行为的方法,就可以在运行时动态改变绿头鸭的飞行行为.有了这个目标,看第二个设计原则:
设计原则:针对接口编程,而不是针对实现编程.
我们来看新的设计.
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了.就算增加一些行为也不会影响到既有的行为类,也不会影响有使用到飞行行为的鸭子类.这样一来,有了继承的好处[复用],也摈弃了继承所带来的缺点.
ok,讲了这么多.就是为了引出策略设计模式(Strategy Pattern).为了介绍这个模式,我们走了很长一段路,下面是这个模式的正式定义:
策略模式:定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户.
关于策略模式的思考和总结以及应用请看设计模式-策略模式(二)。