JavaCoreTech05-类继承的设计技巧


Java核心技术 卷I 第5章 5.8

在本章的最后, 给出一些对设计继承关系很有帮助的建议。

  1. 将公共操作和域放在超类
    这就是为什么将姓名域放在 Person 类中, 而没有将它放在 Employee 和 Student 类中的原因。 2. 不要使用受保护的域
    有些程序员认为, 将大多数的实例域定义为 protected 是一个不错的主意, 只有这样, 子
    e.printStackTraceO ;
    类才能够在需要的时候直接访问它们。 然而, protected 机制并不能够带来更好的保护, 其原

因主要有两点。 第一, 子类集合是无限制的, 任何一个人都能够由某个类派生一个子类, 并 编写代码以直接访问 protected 的实例域, 从而破坏了封装性。 第二, 在 Java 程序设计语言 中, 在同一个包中的所有类都可以访问 proteced 域, 而不管它是否为这个类的子类。
不过, protected 方法对于指示那些不提供一般用途而应在子类中重新定义的方法很有用。 3.使用继承实现“ is-a”关系 使用继承很容易达到节省代码的目的,但有时候也被人们滥用了。例如, 假设需要定义
一个钟点工类。 钟点工的信息包含姓名和雇佣日期, 但是没有薪水。 他们按小时计薪, 并且 不会因为拖延时间而获得加薪- 这似乎在诱导人们由 Employee 派生出子类 Contractor, 然后 再增加一个 hourlyWage 域。
public class Contractor extends Employee {
private double hourlyWage; }
这并不是一个好主意。 因为这样一来, 每个钟点;o橡中都包含了薪水和计时工资这两个域。 在实现打印支票或税单方法耐候, 会带来诞的麻烦, 并且与不采用继承, 会多写很制戈码。
钟点工与雇员之间不属于“ is-a” 关系。钟点工不是特殊的雇员。
4. 除非所有继承的方法都有意义, 否则不要使用继承
假设想编写一个 Holiday 类 3 毫无疑问, 每个假日也是一日, 并且一日可以用 Gregorian
Calendar 类的实例表示, 因此可以使用继承。 class Holiday extends CregorianCalendar { . . , }
很遗憾, 在继承的操作中, 假日集不是封闭的。 在 GregorianCalendar 中有一个公有方法 add, 可以将假日转换成非假日:
Holiday Christmas; Christmas.add(Calendar.DAY_OF_MONTH, 12);
因此, 继承对于这个例子来说并不太适宜。
需要指出, 如果扩展 LocalDate 就不会出现这个问题。 由于这个类是不可变的, 所以没
有任何方法会把假日变成非假日。
5. 在覆盖方法时, 不要改变预期的行为
置换原则不仅应用于语法, 而且也可以应用于行为, 这似乎更加重要。 在覆盖一个方法
的时候, 不应该毫无原由地改变行为的内涵。就这一点而言, 编译器不会提供任何帮助, 即 编译器不会检查重新定义的方法是否有意义。例如,可以重定义Holiday类中add方法“ 修 正” 原方法的问题, 或什么也不做, 或抛出一个异常, 或继续到下一个假日。
然而这些都违反了置换原则。 语句序列
int dl = x.get(Calendar.DAY_OF_MONTH); x.add(Calendar.DAY_OF_MONTH, 1);
int d2 = x.get(Calendar.DAY_OF_HONTH); System.out.println(d2 -dl);
不管 x 属于 GregorianCalendar 类, 还是属于 Holiday 类, 执行上述语句后都应该得到预期的 行 为。
当然, 这样可能会引起某些争议。人们可能就预期行为的含义争论不休。 例如, 有些人 争论说, 置换原则要求 Manager.equals 不处理 bonus 域, 因为 Employee.equals 没有它。 实际 上, 凭空讨论这些问题毫无意义。 关键在于, 在覆盖子类中的方法时, 不要偏离最初的设计 想法。
6. 使用多态, 而非类型信息
无论什么时候, 对于下面这种形式的代码
if (x isoftype1)
actioniM
else if (x isoftype2)
actioniM; 都应该考虑使用多态性。
action, 与 3如 0112 表示的是相同的概念吗? 如果是相同的概念, 就应该为这个概念定义一 个方法, 并将其放置在两个类的超类或接口中, 然后, 就可以调用
X.action0
以便使用多态性提供的动态分派机制执行相应的动作。 使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展。 7. 不要过多地使用反射
反射机制使得人们可以通过在运行时查看域和方法, 让人们编写出更具有通用性的程序。
这种功能对于编写系统程序来说极其实用, 但是通常不适于编写应用程序。 反射是很脆弱的, 即编译器很难帮助人们发现程序中的错误, 因此只有在运行时才发现错误并导致异常。
现在你已经了解了 Java 支持面向对象编程的基础内容: 类、继承和多态。下一章中我们 将介绍两个髙级主题: 接口和 lambda 表达式, 它们对于有效地使用 Java 非常重要。


文章作者: LuckLiu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuckLiu !
  目录