接口是定义混合类型(mixin)的理想选择。 一般来说,mixin 是一个类,除了它的“主类型”之外,
还可以声明它提供了一些可选的行为。 例如, Comparable 是一个类型接口,它允许一个类声明它的
实例相对于其他可相互比较的对象是有序的。 这样的接口被称为类型,因为它允许可选功能被“混合”到
类型的主要功能。 抽象类不能用于定义混合类,这是因为它们不能被加载到现有的类中:一个类不能有
多个父类,并且在类层次结构中没有合理的位置来插入一个类型。
接口允许构建非层级类型的框架。 类型层级对于组织某些事物来说是很好的,但是其他的事物并不
是整齐地落入严格的层级结构中。 例如,假设我们有一个代表歌手的接口,和另一个代表作曲家的接
口:
public interface Singer { AudioClip sing(Song s); } p ublic interface Songwriter { Song compose(int chartPosition); }
在现实生活中,一些歌手也是作曲家。 因为我们使用接口而不是抽象类来定义这些类型,所以单个
类实现歌手和作曲家两个接口是完全允许的。 事实上,我们可以定义一个继承歌手和作曲家的第三个接
口,并添加适合于这个组合的新方法
public interface SingerSongwriter extends Singer, Songwriter { AudioClip strum(); void actSensitive(); }
你并不总是需要这种灵活性,但是当你这样做的时候,接口是一个救星。 另一种方法是对于每个受
支持的属性组合,包含一个单独的类的臃肿类层级结构。 如果类型系统中有 n 个属性,则可能需要支持
2n 种可能的组合。 这就是所谓的组合爆炸(combinatorial explosion)
接口通过包装类模式确保安全的,强大的功能增强成为可能(详见第 18 条)。 如果使用抽象类来
定义类型,那么就让程序员想要添加功能,只能继承。 生成的类比包装类更弱,更脆弱
使用默认方法可以提供实现帮助多多少少是有些限制的。 尽管许多接口指定了 Object 类中方法
(如 equals 和 hashCode )的行为,但不允许为它们提供默认方法。 此外,接口不允许包含实例
属性或非公共静态成员(私有静态方法除外)。 最后,不能将默认方法添加到不受控制的接口中
但是,你可以通过提供一个抽象的骨架实现类(abstract skeletal implementation class)来与接口一
起使用,将接口和抽象类的优点结合起来。 接口定义了类型,可能提供了一些默认的方法,而骨架实现
类在原始接口方法的顶层实现了剩余的非原始接口方法。 继承骨架实现需要大部分的工作来实现一个接
口。 这就是模板方法设计模式[Gamma95]。
按照惯例,骨架实现类被称为 AbstractInterface ,其中 Interface 是它们实现的接口的名
称。 例如,集合框架( Collections Framework)提供了一个框架实现以配合每个主要集合接口:
AbstractCollection , AbstractSet , AbstractList 和 AbstractMap 。 可以说,将它们称
为 SkeletalCollection , SkeletalSet , SkeletalList 和 SkeletalMap 是有道理的,但
是现在已经确立了抽象约定。 如果设计得当,骨架实现(无论是单独的抽象类还是仅由接口上的默认方
法组成)可以使程序员非常容易地提供他们自己的接口实现。
骨架实现类的优点在于,它们提供抽象类的所有实现的帮助,而不会强加抽象类作为类型定义时的
严格约束。对于具有骨架实现类的接口的大多数实现者来说,继承这个类是显而易见的选择,但它不是
必需的。如果一个类不能继承骨架的实现,这个类可以直接实现接口。该类仍然受益于接口本身的任何
默认方法。此外,骨架实现类仍然可以协助接口的实现。实现接口的类可以将接口方法的调用转发给继
承骨架实现的私有内部类的包含实例。这种被称为模拟多重继承的技术与条目 18 讨论的包装类模式密
切相关。它提供了多重继承的许多好处,同时避免了缺陷