本文摘自《Java核心技术 卷 I》第5章继承
继承设计技巧
- 1.将公共操作和字段放在超类中
- 2.不要使用受保护的字段
- 3.使用继承实现“is-a”关系
- 4.除非所有继承的方法都有意义,否则不要使用继承
- 5.再覆盖方法时,不要改变预期的行为
- 6.使用多态,而不要使用类型信息
- 7.不要滥用反射
1.将公共操作和字段放在超类中
2.不要使用受保护的字段
有些程序员认为,将大多数的实例字段定义为protected是一个不错的注意,“以防万一”,这样子类就能够在需要的时候访问这些字段。然而,protected机制并不能够带来更多的保护,这有两方面的原因。第一,子类集合是无限制的,任何一个人都能够由你的类派生一个子类,然后编写代码直接访问protected实例字段,从而破坏了封装性。第二,在Java中,在同一个包中的所有类都可以访问protected字段,而不管他们是否为这个类的子类。
不够,protected方法对于指示那些不提供一般用途而应在子类中重新定义的方法很有用。
3.使用继承实现“is-a”关系
使用继承很容易达到节省代码量的目的,但有时候也会被人们滥用。例如,假设需要定义一个Contractor类。钟点工有姓名和雇佣日期,但是没有工资。他们按小时计薪,并且不会因为拖延时间而获得加薪。这似乎在诱导人们由Employee派生出子类Contractor,然后再增加一个hourlyWage字段。
public class Contractor extends Employee{
private double hourlyWage;
...
}
不过,这并不是一个好主意。因为这样一来,每个钟点工对象中都同时包含了工资和时薪这两个字段。在实现打印薪水或税单的方法时,这会带来无尽的麻烦。与不采用继承相比,使用继承来实现最后反而会多写很多代码。
钟点工与员工之间不属于“is-a”关系。钟点工不是特殊的员工。
4.除非所有继承的方法都有意义,否则不要使用继承
假设想编写一给Holiday类。毫无疑问,每个假日也是一天,并且一天可以用GregorianCalendar类额实例表示,因此可以使用继承。
class Holiday extends GregorianCalendar {
...
}
很遗憾,再继承的操作中,假日集合不是封闭的。GregorianCalendar中有一个公共方法add,这个方法可以将假日装换成非假日:
Holiday christmas;
christmas.add(Calendar.DAY_OF_MONTH, 12);
因此,继承对于这个例子来说并不太适宜。
需要指出,如果扩展LocalDate就不会出现这个问题。由于这个LocalDate类是不可变的,所以没有任何方法能够把假日变成非假日。
5.再覆盖方法时,不要改变预期的行为
替换原则不仅应用于语法,更重要的是,它也适用于行为。在覆盖一个方法的时候,不应该毫无缘由地改变它的行为。就这一点而言,编译器不会提供任何帮助,即编译器不会检查重新定义的方法是否有意义。例如,可以重新定义Holiday类中的add方法来“修正”这个方法的问题,可能什么也不做,或者抛出一个异常,或者是前进到下一个假日。
然而,这样一个“修正”违反了替换原则。对于以下语句序列
int d1 = x.get(Calendar.DAY_OF_MONTH);
x.add(Calendar.DAY_OF_MONTH, 1);
int d2 = x.get(Calendar.DAY_OF_MONTH);
System.out.println(d2 - d1);
不管x的类型是GregorianCalendar还是Holiday,执行上述语句后都应该得到预期行为。
6.使用多态,而不要使用类型信息
只要看到类似下面的代码
if (x is of type1)
action1(x)
else if (x is of type2)
action2(x);
都应该考虑使用多态
action1与action2表示的是相同的概念吗?如果是相同的概念,就应该为这个概念定义一个方法,并将其放置在两个类型的超类或接口中,然后,就可以调用
x.action();
使用多态性固有的动态分派机制执行正确的动作。
使用多态方法或接口实现的代码比使用多个类型检测的代码更易于维护和扩展
7.不要滥用反射
反射机制使人们可以在运行时查看字段和方法,从而能编写出更具有通用性的程序。这种功能对于编写系统程序及其有用,但是通常不适于编写应用程序。反射是很脆弱的,如果使用反射,编译器将无法帮助你查找编程错误,因此只有在运行时才会发现错误并导致异常。