从《Effective Java》中总结
封装,继承,多态是面向对象类语言的三个核心特性,但是我们经常看到教材或者各种书上会讲复合优先于继承,为什么呢?
首先不是说继承不如复合,复合更像是修饰者模式,基类和包装器类的关系,而继承就是父类和子类的关系
下面说的都是基于子类扩展父类(实现继承),而不是接口继承(一个类实现一个接口,或者一个接口扩展另一个一个接口)
一 在实际开发中继承的缺点:
- 与方法调用不同,继承打破了封装性:子类依赖于其超类中的特定功能的实现细节,
- 如果需要覆盖超类的方法,就必须要知道超类所有的方法的内部逻辑,否则会照成意想不到的事故
举个例子:
父类有a,b,c三个方法,而子类覆盖了b,c两个方法,b里面有super.a
,当外部调用子类的的b方法,如果a有c方法的调用,而且你不知道a方法调用了c方法,而且你还修改了c方法的逻辑,那可能会造成意想不到的结果
- 超类的实现会随着发行版本的不同而内部逻辑可能会有变化
- 如果要继承超类,超类必须要有详细的说明文档,否则增加学习成本
- 暴露实现细节,可能导致客户端直接访问这些内部细节
- 限制在原始的实现上,永远限定了类的性能
- 导致语义上的细节
- 可能客户直接修改超类,从而破坏子类的约束条件
以上缺点不适合的范围:首先项目中超类,父类,子类完全由你一个人完成,并且不会有其他人插手,别人也不会扩展你的超类,基类,也不会使用你完成的子类。
二 复合相比较于继承的优点:
- 通过在新类增加一个私有域,引用原本的超类(后面同意叫需要叫现有类),使现有类变成新类的一个组件,而新类的方法都可以调用现有类里面的对应的方法,这个也叫转发
- 没有打破封装,就算现有类添加新的方法,或者修改原来方法的逻辑(方法入参和返回结果不能有改变),也不会影响到新类,对于封装的优点自行查询
复合和转发的结合也被宽松的称为"委托"
三 复合的缺点:
不适合回调框架,因为回调框架是把对象自身的引用传递给其他的对象,用于后续的调用,但是包装起来的对象并不知道它外面的对象,所以它传递一个执行自身的引用,回调时避开了外面的包装对象,这也被称为SELF问题
四 如何选复合还是选继承
在《Effective Java》中的选择方法
- 是否正在扩展超类?
- 它的API有没有缺陷?
- 是否愿意把那些缺陷传播到子类的API中?
- 是否存在父子类型关系?
即使存在父子关系,一样可以用复合,而且超类设计的不好,会很影响子类
在我遇到的实际项目中:
- 用到继承的大部分超类都是基类,或者子类必须要覆盖父类的指定方法
- 而对于扩展的功能基本上都用的复合
四 用到继承时,一定要有文档
而现实是我们用到继承的地方,也就在超类上写明超类的含义,可能方法都不写注释,所以现在我一直提倡注释不要怕写多,只要能描述清楚,不写注释还要我们研究代码,浪费时间,
- 超类的构造器不要调用子类可覆盖的方法(直接间接都不行)
- clone和readObject不可调用可覆盖的方法(直接间接都不行)
- 对于不想让子类覆盖的方法,就要禁止此方法子类化