这一章在讨论的问题
Using inheritance hasn't worked out very well, since the duck behavior keeps changing across the subclasses, and it's not appropriate for all subclasses to have those behaviors.
对swim()
和quack()
方法的讨论
鸭子分为两类,一类是生物鸭子,一类是模拟鸭子。
情况一
假设生物鸭子的swim()
和quack()
方法都是一样的;
假设模拟鸭子的swim()
和quack()
方法都是特殊的。
那么相应的类图如下:
Duck
中的swim()
和quack()
都是抽象方法。
情况二
假设生物鸭子的swim()
和quack()
方法都是一样的;
假设一部分模拟鸭子的swim()
和quack()
方法跟生物鸭子一样,一部分是特殊的。
如何重用鸭子中的swim()
和quack()
方法?是不是要把swim()
和quack()
的实现放到一个类中呢?
所有的生物鸭子都继承
AbtractCommonDuck
,如果模拟鸭子的swim()
和quack()
方法跟生物鸭子一样就继承AbtractCommonDuck
,否则继承Duck
。
情况三
假设鸭子的swim()
和quack()
方法都是一样的;
假设模拟鸭子的swim()
和quack()
方法都是一样的,但是跟鸭子的不一样。
那么相应的类图如下:
总结
假设生物鸭子swim()
和quack()
方法也是有特殊情况的话,那么模拟鸭子和鸭子的组合就有很多种情况了,但是我是不会因为要重用swim()
和quack()
这个两个方法就把类图搞得乱七八糟的,对于我来说,类图就应该这样设计:
为什么要这样?因为鸭子就是这样分类的,软件是在模拟现实,而且这样的分类对大家来说都是通俗易懂的。
对于
swim()
和quack()
方法的具体实现我会放在Duck
,或者是两个子类中。对于书中的只有
Duck
类也是可以的,即不区分生物鸭子和模拟鸭子:对fly()
和quack()
方法的讨论
在父类Duck
中添加fly
方法本身就是不适合的。我们必须明确飞行能力并非所有鸭子都有,比如橡皮鸭子就不具有飞行能力,所以把fly()
放在父类中是否合适就很值得商榷。
是否要把fly()
和quack()
加入Duck
?
可是加进去之后如果以后鸭子的行为有增加或删除,那么这个类体系中的所有类都要进行修改。 ,以下是书中
大家觉得合适吗?明明有些鸭子没有飞行的能力,你却给它加入了飞行的能力。
我认为可以从几个角度进行思考:
1.这是一款鸭子应用,不管有多少种鸭子,都不会突然冒出一只鸡;用户、系统所应对的都是鸭子,没有其他任何东西,这对于该系统来说是最简单的;即使以后鸭子有了其他行为,比如跑步的方法,改起来是很痛苦的,但是对于系统和用户来说,他们都只需要应付鸭子这个接口,无需理会其他事情。
2.这是一个很糟糕的设计,如果有新种类的鸭子加入,你就可能需要修改整个类体系,这跟一开始的做法有什么区别?但是问题是我拥有一只鸭子,我知道它能不能飞呢?我不知道,于是我这样使用:
((FlyBehavior)duck).fly()
你喜欢这种方式吗?你喜欢你的系统到处都需要进行这种检查吗?
到底是什么在变化?
鸭子的种类有很多,每种鸭子的行为是不可预测的。
这一章的内容导读
继承——现在我们得让鸭子能飞
在《但是,可怕的问题发生了》只是说明“对代码所做的局部修改,影响层面可不只是局部”,“当涉及“维护”时,为了“复用”目的而使用继承,结局并不完美”。
继承的问题是必须确保父类中的实现对子类来说是合适的,所以必须仔细检查从父类中继承过来的行为是否是恰当的。
Joe想到继承
这里使用继承最大的缺点就是代码在多个子类中重复。
运行时的行为不容易改变倒不是什么大问题,因为一种鸭子的行为一般都是固定的,一个鸭子不可能会横着飞又会竖着飞,难不成是钢铁侠?
改变会牵一发动全身,造成其他鸭子不想要的改变。由于实现是在父类中的,如果父类改变了它的实现,所有继承并没有重写该实现的鸭子都会受到波动,如果你不清楚有哪些子类没有重写该方法,那么就会有隐藏着的问题出现。
很难知道所有鸭子的全部行为。
- 这个真的不知道在说什么,使用策略模式就知道鸭子的全部行为了吗?我想它的意思是如果我知道鸭子的全部行为的话,就可以跟我在《 对
swim()
和quack()
方法的讨论》中讨论的一样,我们可以实现很多个抽象子类(就不会导致代码在多个子类中重复),可惜我们不知道。或者是另外一个意思,我不知道鸭子的全部行为,所以我不能在父类中把这些行为都写上,然后根据鸭子的种类判断执行那个操作。(说多都是泪,根本就看不懂。) - 第二种解释是我不知道所有鸭子的全部行为,所以如果我改变了父类中的实现,我不清楚会对哪些子类会产生影响。
- 第三种解释是我不清楚所有鸭子的全部行为,
书中在说继承的缺点,但是却并不是说这样设计类是不对的,Duck
类是一定要包括所有种类的鸭子的行为的,而是说使用继承,不能解决代码重复的问题。
利用接口如何?
每当有新的鸭子子类出现,他就要被迫检查并可能需要覆盖
fly()
和quark().....这简直是无穷无尽的噩梦。
因为子类继承了父类的行为,所以每个子类都必须好好检查这些行为的实现,如果不适当就必须进行修改,万一忘记修改了,就会导致子类继承了父类不恰当的行为。
并非“所有”的子类都具有飞行和呱呱叫的行为,所以继承并不是恰当的解决方式。
在父类中的实现会被子类继承,如果子类没有好好检查并覆盖父类中的方法,就可能会导致不恰当的行为被继承下来。