网站首页 > java教程 正文
原文
5.10 继承的设计技巧
在本章的最后,我会给出使用继承时很有用的一些技巧。
- 将公共操作和字段放在超类中
- 正是因为这个原因,我们将姓名字段放在 Person 类中,而没有将它重复放在 Employee 和Student 类中。
- 不要使用受保护的字段。
有些程序员认为,将大多数的实例字段定义为 protected 是一个不错的主意,“以防万一”,这样子类就能够在需要的时候访问这些字段。不过,protected 机制并不能提供太多保护,这有两方面的原因。第一,子类集合是无限制的,任何一个人都能够由你的类派生一个子类,然后编写代码直接访问 protected 实例字段,从而破坏封装性。第二,在Java 中,同一个包中的所有类都可以访问 proteced 字段,而不论它们是否为这个类的子类。
?不过,有些方法不打算作为通用方法,要在子类中重新定义,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 但不是 final 类。如果派生一个Holiday 子类,就没有任何方法能够把假日变成非假日。
5. 覆盖方法时,不要改变预期的行为。
?替换原则不仅应用于语法,更重要的是,它也适用于行为。覆盖一个方法的时候,不应该毫无缘由地改变它的行为。就这一点而言,编译器不会提供任何帮助,编译器不会检查你重新定义的行为是否有意义。例如,可以重新定义 add 来“修正”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,执行上述语句都应该有预期的行为。当然,这是个难题。理智和不理智的人们可能就预期行为是什么争论不休。例如,有些人争论说,替换原则要求 Manager.equals 忽略 bonus 字段,因为 Employee.equals 就忽略了这个字段。实际上,凭空讨论这些问题毫无意义。归根结底,关键在于在子类中覆盖方法时,不要偏离最初的设计初衷。
6. 使用多态,而不要使用类型信息
只要看到以下形式的代码
if (x is of type 1)
?action1(x);
else if (x is of type 2)
?action2(x);
都应该考虑使用多态。
?action1与action2表示的是一个通用概念吗?如果是,就应该将这个概念定义为这两个类型的公共超类或接口中的一个方法。然后,就可以调用
?x.action();
并利用多态性固有的动态分派机制执行正确的动作。
与使用多个类型检测的代码相比,使用多态方法或接口实现的代码更易于维护和扩展。
7. 不要滥用反射。
?反射机制使人们可以在运行时查看字段和方法,从而能编写出极具通用性的程序。这种功能对于系统编程极其有用,但是通常并不适合编写应用程序。反射很脆弱,如果使用反射,编译器将无法帮助你查找编程错误,直到运行时才会发现错误并导致异常。
?现在你已经了解了 Java 如何支持面向对象编程的基础:类、继承和多态。下一章中我们将介绍两个高级主题:接口和 lambda 表达式。它们对于有效地使用 Java 非常重要。
猜你喜欢
- 2025-01-14 Java动态代理原理图解(附2种实现方式详细对比)
- 2025-01-14 java 面向对象编程:封装、继承、多态
- 2025-01-14 java面向对象三大特性:封装、继承、多态——举例说明(转载)
- 2025-01-14 为什么 Java 不支持类多重继承?
- 2025-01-14 Java:Java中的多重继承问题
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)