本系列是《Head First Design Patterns》读书笔记加之个人理解,如有错误,请纠正。
问题提出
假设你设计了一个简单的展示鸭子的app。
设计了一个父类Duck,其他各种类型的鸭子都继承这个父类。
但是现在有了一个新的需求,鸭子要能够飞。
聪明的你很快的在父类Duck里面添加了fly()这个函数。
但是问题来了,在演示的时候发现有一个继承Duck的橡胶小黄鸭也跟着飞了起来!!!这不符合科学道理。
这是后你发现事情直接添加fly()在父类是一个愚蠢的做法。
那么我可以在RubberDuck(橡胶小黄鸭)这个类中重新fly()这个方法,什么也不做,这样就相当于没有这个函数了。
然而,如果有一个木头鸭子,你继承了Duck后,它将有更多的不能够实现的方法,难道都需要重写么,这显然增加了很多工作量。
并且你的app是每个月都更新的, 那么如果你一直使用override去覆盖某些函数的方法,那么你将永远都都要检查每个新的子类是否覆盖了。
问题一度陷入僵局。聪明的你一排脑瓜。使用接口怎么样?
如果把飞的功能fly()和叫的功能quack()从Duck父类中抽离出来,单独制作成接口Flyable()接口 和 Quackable接口, 那么所有拥有这两个功能的子类只要实现这两个接口就好了。
这个想法看似很好,但是,如果你想要更改一下fly()函数,你难道要讲几十或者几百的子类中的fly都重新修改吗??!!而且在编写子类的时候,代码重复书写也是一个问题!
这时可以看出一个好的OO设计原则的重要性。
在软件开发中,没有什么是永恒不变的。无论你设计的多么巧妙。
解决
使用继承父类的功能添加会影响所有的子类,而使用接口又丧失了代码的重用行,并且难以维护。幸运的是,针对这种情况有一个设计规则:
准则一:
确定不变与变的,并把它们分离。
这个原则的另一种理解:将变化的部分封装起来,以便以后可以更改或扩展变化的部分,而不影响其他部分。
这个原则虽然简单,但是几乎所有的设计模式都提供了一种让变化的部分独立于其他部分的方法。
对于鸭子这个问题,我们将fly()和quack()这两个功能抽离出来(假设鸭子的其他功能是不变的。),形成两个类的子集(完全独立于Duck类),例如飞的子集
包含各种飞的类。这些子集成为Duck Behaviors.
如何设计Duck Behaviors?
如果我们想要实例化一个MallardDuck的实例,并且指定某个特定的fly行为(从fly的子集中指定某一个), 那么如果能够动态的改变这个实例的行为就好极了。也就是说我们在Duck父类中添加能够设定行为的setter方法。这样一来在运行时就可以使用setter()来设定某种鸭子的具体行为。
为了实现这个目标,就有了第二个设计准则:
program to an interface, not a implementation.
编程到接口,而不是实现。用接口接收创建的对象。
如何实现?
- 设计两个
interface
,分别叫做FlyBehavior和QuackBehavior。这两个接口分别定义fly() 和 quack() - 对于fly系列的不同功能,可以创建许多实现FlyBehavior接口的类,如FlyWithWings, FlyNoWay,里面实现fly方法。
- Quack的功能同fly
- 对于Duck这个父类来说,直接移除响应的功能函数,取而代之的是使用接口。
public class Duck{
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
//其他参数 ...
//初始化
//...
//setter 函数
public void performFly(){
flyBeahvior.fly();
}
}
- 对于子类
public class MallardDuck extends Duck{
public MallardDuck(){
//使用接口来处理, 父类设置响应的setter函数,那么就可以在运行时动态改变Behavior
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}
面向接口与面向超类
不使用interface,也可以做到program to an interface,方法是通过多态。建立一个abstract类,
programming to an implementation:
Dog d = new Dog();
d.bark();
programming to an interface/supertype
Animal animal = new Dog();
animal.makeSound();
甚至可以在运行时分配,不用具体了解是什么类型的animal
Animal a ;
a = getAnimal();
a.makesound();