5.8 继承与组合
继承是实现类复用的重要手段,但继承带来了一个最大的坏处:破坏封装。
相比之下,组合也是实现类复用的重要方式,而采用组合方式来实现类复用则能提供更好的封装性。
下面将详细介绍继承和组合之间的联系与区别。
5.8.1 使用继承的注意点
子类扩展父类时,子类可以从父类继承得到成员变量和方法,如果访问权限允许,子类可以直接访问父类的成员变量和方法,相当于子类可以直接复用父类的成员变量和方法。
继承会破坏父类的封装性
继承带来了高度复用的同时,也带来了一个严重的问题:继承严重地破坏了父类的封装性。
前面介绍封装时提到:每个类都应该封装它内部信息和实现细节,而只暴露必要的方法给其他类使用。但在继承关系中,子类可以直接访问父类的成员变量(内部信息)和方法,从而造成子类和父类的严重耦合。
从这个角度来看,父类的实现细节对子类不再透明,子类可以访问父类的成员变量和方法,并可以改变父类方法的实现细节(例如,通过方法重写的方式来改变父类的方法实现),从而导致子类可以恶意篡改父类的方法。
父类设计规则
为了保证父类有良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下规则
- 尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置成
private
访问类型,不要让子类直接访问父类的成员变量 - 不要让子类可以随意访问、修改父类的方法。
- 父类中那些辅助其他的工具方法,应该使用
private
访问控制符修饰,让子类无法访问该工具方法; - 如果父类中的方法需要被外部类调用,则必须以
public
修饰,- 如果不希望子类重写该方法,可以使用
final
修饰符来修饰该方法;
- 如果不希望子类重写该方法,可以使用
- 如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用
protected
来修饰该方法。
- 父类中那些辅助其他的工具方法,应该使用
- 尽量不要在父类构造器中调用将要被子类重写的方法。
父类构造器调用被重写方法时容易发生错误
1 | class Base { |
上面的代码中,子类增加了父类没有的name
属性,并重写的父类的test
方法,子类重写的test
方法会隐藏父类的同名方法
在创建子类时,会先调用父类的构造器,
- 由于在父类构造器中调用了
test
方法, - 由于子类重写的
test
方法会隐藏父类的同名方法,
所以父类构造器调用的是子类重写的test
方法,子类重写的test
方法需要调用子类新增的成员变量name
,但该name
成员变量没有初始化,此时引用变量name
的值为null
,所以name.length()
方法会引出空指针异常.
什么时候需要派生子类
从父类派生新的子类需要具备以下两个条件之一。
- 子类需要额外增加属性,而不仅仅是属性值的改变。例如从
Person
类派生出Student
子类,Person
类里没有提供grade
年级)属性,而Student
类需要grade
属性来保存Student
对象就读的年级,这种父类到子类的派生,就符合Java
继承的前提。 - 子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)。例如从
Pcrson
类派生出Teacher
类,其中Teacher
类需要增加一个teaching0
方法,该方法用于描述Teacher
对象独有的行为方式:教学。
原文链接: 5.8 继承与组合 5.8.1 使用继承的注意点