设计抽象类或接口时需要注意的地方

1、要么为继承而设计,并提供文档说明,要么就禁止继承

首先,该类的文档必须精确地描述覆盖每个方法所带来的影响。换句话说,该类必须有文档说明它可覆盖的方法和自用性。对于 每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的(所谓可覆盖的方法是指非final的,仅有的或受保护的)。更一般地,类必须在文档中说明,在哪些情况下它会调用可覆盖的方法。

在发布类之前,必须先编写子类对类进行测试

为了允许继承,类还必须遵守其它一些约束。构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。

无论是clone方法还是readObject,都不可以调用可覆盖的方法不管是以直接还是间接的方式。

对于那些并非为了安全地进行子类化而设计和编写文档类,要禁止子类化。有两种方式可以禁止子类化,一是用final修饰类,二是但构造器设计为私有的,并设计一些公有的静态的构造工厂来代替工厂类。

如果具体的类没有实现标准的接口,那么禁继承可能会给有些程序员带来不便,如果你认为必须允许从这样的类继承,一种合理的办法是确保这个类永远不会调用它的任何可覆盖的方法,并在文档中说明这一点。换句话说,完全消除这个类中可覆盖方法的自用特性。这样做之后,就可以创建能够安全地进行子类化“的类。覆盖方法将永远也不会影响其它任何方法的行为。

你可以机械地调用可以覆盖方法的自用特性,而不改变它的行为。将每个可覆盖方法的代码体移到一个私有的”辅助方法“中,并且让每个可覆盖的方法调用 它的私有辅助方法。然后,用”直接调用可覆盖方法的私有辅助方法“来代替”可覆盖方法的每个自用调用“


2、接口优于抽象类

现有类可以很容易被更新,以实现新的接口

接口是定义mixin的理想选择

接口允许我们构造非层次结构的类型框架

虽然接口不允许包含方法的实现,但是,使用接口来定义类型并不妨碍你为程序提供实现上的帮助。通过对你导出的每个重要接口 都提供一个抽象的骨架实现类,把接口和抽象类的优点结合起来。接口 的作用仍然是定义类型,但是骨架实现类接管了所有与接口实现相关的工作。

骨架实现的美妙之处在于,它们为抽象类提供了实现上的帮助,但又不强加”抽象类被用作类型定义时“所持有的严格限制。对于接口的大多数实现来讲,扩展骨架实现类是个很显然的选择,但并不是必需的。

一般来说,要想在仅有接口中增加方法,而不破坏实现这个接口所有实现的类,这是不可能的。因此,在设计公有接口要非常谨慎。接口一但被公开,并且已被广泛使用,再想改变这个接口几乎是不可能的。

简而言之,接口通常是定义允许多个实现的类型的最佳途径。这条规则有个例外,即当演变的容易性比灵活性和功能更为重要的时候。在这种情况下,应该使用抽象类来定义类型,但前提是必须理解并且可以接受这些局限性。如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。最后 ,应该尽可能地谨慎地设计 所有 的公有接口,并且通过编写多个实现来对它们进行全面的测试。

3、接口只用于定义类型

有一种接口 被 称为常量接口,它不满足上面的条件。这种接口没有包含任何方法,它只包含静态的final域,每个域都导出一个常量。使用这些常量的类实现这个接口,又避免用类名来修饰常量名。

常量接口模式是对接口 的不良使用。类在内部使用某些常量这纯粹是实现细节。实现常量接口 ,会导致把这样的实现细节泄露到该类的导出API中。类实现常量接口,这对于这个类的用户来讲并没有什么价值。实际上,这样做反而会使他们更加糊涂。更糟糕的是,它代表了一种承诺:如果在将来的发行版本中,这个类被修改了,它不再需要使用这些常量了,依然必须实现这个接口,又确保二进制兼容性。如果非final类实现了常量接口,它的所有子类的命名空间也会被接口中的常量所”污染“

简而言之,接口应该被用来定义类型,而不应该被用来导出常量。


4、类层次优于标签类

标签类有着许多的缺点。它们中充斥着样板代码,包括枚举声明、标签域以及条件语句。由于 多个实现乱七八糟地挤在了单个类中,破坏了可读性。内存占用增加了,因为实例承担着属于其他风格的不相关的域,产生更多的样板代码。总之:标签类过于冗长,容易出错并且效率低下。

层次化,子类化,每个类型的实现都配有自己的类,这些类都没有受到不相关的数据域的拖累。所有 的域都是final的。编译器确保每个类的构造器都初始化它的数据域,对于根类中声明的每个抽象方法,都确保有一个实现。这样就杜绝了由于 遗漏switch case而导致运行时失败的可能性。多个程序员可以独立地扩展层次结构,并且不用访问源代码就能相互操作。每种类型都有一种相关的独立的数据类型,允许程序员指明变量的类型,限制变量,并将参数输入到特殊的类型。

类层次的另一种好处在于,它们可以用来反映类型之间本质上的层次关系,有助于增强灵活性,并进行更好的编译时类型检查。

简而言之:标签类很少有适用的时候。当你想要编写一个包含显示标签域的类时,应该考虑一下,这个标签是否可以被取消,这个类是否可以用层次来代替。当你遇到 一个包含标签域的现有类时,就要考虑将它重构到一个层次结构中去。


5、用函数对象表示策略

函数指针的主要用途主要是实现策略模式。为了在Java中实现这种模式,要声明一个接口 来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例 化这个具体策略类,当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的表态final域被导出,其类型为该策略接口。(不理解这段的意思)


6、优先考虑静态成员类

嵌套类:是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为它的外围类提供服务。如果嵌套类将来可能会用于其他的某个环境中,它就应该是顶层类。嵌套类有四种:静态成员类、非静态成员类、匿名类和局部类。除了第一种外,其它三种都被称为内部类。

静态成员类是最简单的一种嵌套类。最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,也遵守同样的可访问性规则。如果它被声明为私有的,它就只能在外围类的内部才可以被访问

从语法上讲,静态成员类和非静态成员类之间的唯一区别是,静态成员类的声明中包含修饰符static。尽管它们的语法非常相似,但是这两种嵌套类有很大的区别。非静态成员类的每个实例都隐含着与外围类的一个外围实例相相关联。在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用。如果嵌套类的实例可以在它的实例之外独立存在,这个嵌套类就必须是静态成员类:在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。

当非表态成员类的实例被创建的时候,它和外围实例之间的关系也随之被创建起来。而且,这种关联关系以后都不能被修改。

非静态成员类的一种常见用法是定义一个Adapter,它允许外部类的实例被看作是另一个不相关的类的实例。

如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为表态成员类,而不是非静态成员类。如果省略了static修饰符,则每个实例都将包含一个额外的指向对象的引用。保存这份引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时去却仍然得以保留。如果在没有外围实例的情况下,也需要分配实例,就不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。

私有静态成员的一种常见用法是用来代表外围类日所代表的对象的组件。

如果相关的类是导出来类的仅有的或受保护的成员,毫不疑问,在静态和非静态成员类之间做出正确的选择是非常重要的。

匿名类的适用性受到诸多的限制。除了在它们被声明的时候之外,是无法将它们实例化的。你不能执行instanceof测试,或者做任何需要命名类的其他事情。你无法声明一个匿名类来实现多个接口,或者扩展一个类,并同时扩展类和实现接口。匿名类的客户端无法调用任何成员,除了从它的超类型中继承得到之外。由于匿名类出现在表达式中,它们必须保持简短-------大约10行或者更少些--------否则会影响程序的可读性。

匿名类的一种常见的用法是动态地创建函数对象。


摘抄自:Effective Java

发布了25 篇原创文章 · 获赞 5 · 访问量 4万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章