上一节讲到利用继承思想每次更改鸭子的种类都会被迫检查可能需要覆盖的fly()和quark()方法。。。这简直是无穷无尽的噩梦。
所以需要一个更清晰的方法让某些鸭子类型可飞或可叫。
这样的话就发现重复代码会变得非常多。而且对于多种鸭子比如50个鸭子的飞行类都需要稍微修改一下的话那么每个鸭子都需要修改。
我们知道并非所有的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。虽然Flyable与Quackable可以解决一部分问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用,这只能算是从一个噩梦跳进另一个噩梦。甚至,会飞的鸭子中,飞行动作可能还有多种变化。。。
此时用设计模式可以解决以上问题。用老方法找出一个解决之道:“采用良好的OO软件设计原则”
设计原则:找出应用中可能需要的变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
把会变化的部分提取并“封装”起来,好让其他部分不会收到影响。结果让代码引起的不经意后果变少,系统变得更有弹性。
此时我们就要分开变化和不变化的部分
就目前而言除了fly()和quack()问题外,Duck类还算一切正常,似乎没有特别需要修改的部分。现在分开“变化和不会变化的部分”我们要建立两组类(完全远离Duck类)一个是“fly”相关的,一个是“quack”相关的,每组类实现各自的动作。比方说,我们可能有一个类实现“呱呱叫”,另一个类实现“吱吱叫”,还有一个类实现“安静”。
我们知道duck类中的fly()和quack()会随着鸭子的不同而改变。为了要把这两个行为从Duck类中分开,我们将把它们从duck类中取出来,建立一组新类来代表每个行为。
如何设计飞行和呱呱叫的行为的类呢?看第二个设计原则
设计原则:针对接口编程,而不是针对实现编程
我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。
“针对接口编程”的真正意思是“针对超类型编程”
这里所谓的“接口”有多个含义,接口是一个“概念”,也是一种JAVA的interface构造。你可以在不涉及Java interface的情况下,“针对接口编程”,关键就在多态,利用多态,程序可以针对超类型编程,执行时会根据实际情况执行到真正的行为,不会被绑死在超类型的行为上。“针对超类型编程”这句话,可以更明确地说成“变量声明应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型!”
下面一个简单多态的例子:假设有两个具体的实现(Dog和Cat)继承Animal类
Dog d = new Dog();
d.bark();
这里声明变量“d”为Dog类型(是animal的具体实现),会造成我们必须针对具体实现编码。
但是,“针对接口/超类型变成”做法会如下:
Animal animal = new Dog();
animal.makeSound();
更棒的是,子类实例化的动作不再需要在代码中硬编码,例如new Dog(0,而是在运行时才指定具体实现对象。
a = getAnimal();
a..makeSound();
我们不知道实际的子类型是什么,我们只关心他知道如何正确的进行makeSound()动作就够了。
好了现在我们再回来,如何实现鸭子的行为
在此我们有两个接口,FlyBehavior和QuackBehavior,还有他们对应的类,负责实现具体的行为:
好了我们已经抽离出变化的和不变化的代码来了,那么如何整合鸭子的行为,请看下一节。