[转]继承设计技巧

来源:互联网 发布:程序员副业 编辑:程序博客网 时间:2024/05/09 09:17

摘抄自:《Java2核心技术 卷I:基础知识》(原书第七版 中文版)

1.将公共操作和域放置在超类。
    这就是为什么将姓名(name)域放在Person类中,而不将它放置在它Person类的子类Employee类和Studunt类中的原因。
2.不要使用受保护的域。
    protected机制并不能够带来更好的保护,其原因主要由两点。第一,子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问protected的实例域,从而破坏了封装性。第二,在Java语言中,在同一个包中的所有类都可以访问protected域,而不管它是否是这个类的子类。
3.使用继承实现“is-a”关系。
    使用继承很容易达到节省代码的目的,但有时候也被人们滥用了。例如,假设需要定义一个钟点工(Contractor)类。钟点工的信息包含姓名和雇用日期,但没有薪水。他们按小时计算薪水,并且不会因为拖延时间而获得加薪。这似乎在诱导人们由Employee类派生出子类(Contractor),然后增加一个hourlyWage域。
    class Contrator 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);
    因此,继承对于这个例子来说并不太适宜。
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类,执行上述语句后都应该得到预期的行为。
    当然,这样可能会引起某些争议。人们可能就预期行为的含义争论补休。例如,有些人争论说,置换原则要求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.不要过多地使用反射。
    反射机制使得人们可以通过在运行时查看域和方法,让人们编写出更具有通用性的程序。这种功能对于编写系统程序来说极其使用,但是通常不适于编写应用程序。反射是很脆弱的,即编译器很难帮助人们发现程序中的错误。任何错误只能在运行时才被发现,并导致异常。

原创粉丝点击