策略模式
1)首先我们设计一个鸭子游戏,根据OO技术,我们首先想到的是建一个超类,然后用超类去处理所有鸭子共有的能力(比如呱呱叫、游泳),然后由其他类别的鸭子去继承这个超类,在派生类中去实现每个鸭子独有的特征。
新需求:现在我们要使一些鸭子具有会飞的能力,怎么办?
想到:我们可以在超类中增加fly()方法,然后所有鸭子都去继承这个fly()。
问题:然后我们发现,一些不应该具备飞这个能力的鸭子也会飞了?(比如橡皮鸭子)怎么办?
此时我们会发现,我们针对代码所做的局部修改,影响层面远远不只是局部。
解决:我们想到可以在派生类中去覆盖掉超类的fly()方法。让橡皮鸭子在fly()方法中啥也不干。
发现:我们发现用继承的方式去实现上述的鸭子游戏,会导致诸多缺点:
1.代码在多个子类中重复;
2.运行时的行为不容易改变;
3.很难知道所有鸭子的全部行为;
4.改变会牵一发动全身,造成所有鸭子不想要的改变。
2)那我们考虑一下,改用接口怎么样?
我们将飞和叫的能力从鸭子超类中拿出来,分别放入到Flyable和Quackable接口中。(因为并不是所有的鸭子都会飞,或者都会叫)
问题:这样是解决了我们不厌其烦地去覆盖超类方法的机械工作,但是我们仿佛又跳入了另外一个大坑:代码无法复用。
比如,现在我们要稍稍修改一下会飞鸭子的飞行行为,怎么办?只能一个一个地去修改每一个具体地鸭子,这简直是个更大的恶梦。
对于这个问题我们会用老办法找出一个解决之道:
“采用良好的OO软件设计原则”。
3)解决问题的设计原则
把会变化的部分取出并“封装”起来,好让其他部分不会受到影响。
这会让我们的代码变化引起的不经意后果变少,系统变得更有弹性。
4)实施
我们知道Duck类中的fly()和quack()会随着鸭子的不同而改变,为了要把这两个行为从Duck类中分开,我们将它们从Duck类中取出,建立一组新类来代表每个行为。
那么怎么设计实现飞行和呱呱叫的行为的类呢?
根据第二个设计原则:
我们用接口去代表每个行为。所以这次鸭子类不会负责实现Flying和Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就称为“行为”类。
这种做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑得死死的,没有办法改变代码(除非写更多的代码)。
在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。(换句话说,特定的具体行为编写在实现FlyBehavior与QuackBehavior的类中)。
问题:为什么非要把FlyBehavior设计成接口,而不使用抽象超类呢?使用抽象超类不就可以使用多态了吗?
其实,抽象超类型可以是“抽象类”或“接口”。
“针对接口编程”真正的意思是“针对超类型(supertype)编程”。
这里所谓的“接口”有多个含义,接口是一个“概念”,也是一种Java的interface构造。你可以在不涉及Java interface的情况下,“针对接口编程”,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。“针对超类型编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型”。
5)实现鸭子的行为
问题:那本例中Duck是不是也该设计成一个接口?
在本例中,这么做并不恰当。如你所见的,我们已经让一切都整合妥当,而且让Duck成为一个具体类,这样可以让衍生的特定类(如绿头鸭)具有Duck共同的属性和方法。我们已经从Duck的继承结构中删除了变化的部分,原先的问题都已经解决了,所以不需要把Duck设计成接口。
6)整合鸭子的行为
关键在于,鸭子现在会将飞行和呱呱叫的动作“委托”(delegate)别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。
1.首先,在Duck类中“加入两个实例变量”,分别为“flyBehavior”与“quackBehavior”,声明为接口类型(而不是具体类型),每个鸭子对象都会动态地设计这些变量以在运行时引用正确的行为类型(例如:FlyWithWings、Squeak等)。
问题:我们不是说不对具体实现编程吗?但是我们在上述构造器里不是正在制造一个具体的Quack实现类的实例么?
是的。暂时我们确实是这样做的,但是在后续学到更多的模式时,就可以修正这一点。
虽然我们把行为设定成具体的类(通过实例化类似Quack或FlyWithWings的行为类,并把它指定到行为引用变量中),但是还是可以在运行时“轻易地”改变它。
所以,目前的做法还是很有弹性的,只是初始化实例变量的做法不够弹性罢了。但是这些实例变量又都是接口类型,我们在运行时可以通过多态的方式动态地给它指定不同的实现类。
7)封装行为的大局观
8)小结
OO基础:
- 抽象
- 封装
- 多态
- 继承
OO原则:
- 封装变化
- 多用组合、少用继承
- 针对接口编程,不针对实现编程
OO模式:
策略模式——定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
要点:
- 知道OO基础,并不足以让你设计出良好的OO系统
- 良好的OO设计必须具备可复用性、可扩充、可维护三个特性
- 模式被认为是历经验证的OO设计经验
- 模式不是代码,而是针对设计问题的通用解决方案。你可以把他们应用到特定的应用中
- 模式不是被发明,而是被发现
- 大多数的模式和原则,都着眼于软件变化的主题
- 大多数的模式都允许系统局部改变独立于其他部分
- 我们常把系统中会变化的部分抽出来封装
- 模式让开发人员之间有共享的语言,能够最大化沟通的价值
参考书目:《Head First 设计模式》