隐式接口实现

2006年1月4日

Java 和 C# 都共享相同的纯接口类型模型。您可以通过 interface Mailable 声明一个纯接口,然后您可以声明您使用 class Customer implements Mailable(在 Java 中)来实现它。一个类可以实现任意数量的纯接口。这个模型忽略的一件事是,只要您有一个类,您就有一个*隐式*接口。

公共隐式接口 Customer 是 Customer 上声明的所有公共成员。(在我见过的每种面向对象语言中都存在这种隐式接口。)Java 和 C# 都不允许您做的一件事是实现隐式接口 - 也就是说,您不能编写 class ValuedCustomer implements Customer

实现隐式接口意味着什么?本质上,它会告诉类型系统 ValuedCustomer 类实现了 Customer 公共接口中声明的所有方法,但不采用其任何实现,即其公共方法体以及非公共方法或数据。换句话说,我们有接口继承,但没有实现继承。

这相当于将 Customer 转换为一个包含 Customer 所有公共方法的接口,然后拥有一个实现该接口的 CustomerImpl 类。

为什么这可能有用?我记得过去的一个案例是在 Java 的早期,在当前的集合框架出现之前。我们想用我们自己的实现替换 Vector 类,但不能,因为 Vector 是一个类,我们只能继承它。有时,当库没有提供接口来允许自由替换时,您会遇到这样的情况,如果没有此功能,我们就会陷入困境。

这在今天的测试中尤为突出。很多时候,您想 stub 出一些东西,但除非您有一个接口,否则这很难或不可能做到。这也导致定义纯接口类型,而这样做的唯一原因是为了支持测试的替换。虽然使用 接口实现对 是一种常见的方法,但我们中的许多人并不喜欢这种方法。隐式接口实现将是一种更简洁的方法。

那么为什么语言不允许这样做呢?我真的不知道 - 但我不是语言设计师。我曾经有机会问 Anders Heljsberg 关于这个问题,他的回答与他只喜欢在您显式地将成员声明为虚拟的情况下才进行覆盖的偏好非常相似。本质上,它是担心子类(或本例中的实现者)破坏超类,这涉及到一个更广泛的主题,即如何使用子类。然而,这只是在晚餐时的一次简短对话,所以我不知道我们是否真的充实了讨论。

写完这篇文章后,我的老同事 Mike Rettig 指出,这样做的问题之一是类实际上定义了多个隐式接口。例如,在 Java 中,customer 类实际上定义了四个隐式接口:public、protected、package 和 private。如果一个对象与 Customer 协作,它可能会使用这些接口中的任何一个(customer 的另一个实例可以使用私有特性。)如果我们想实现一个隐式接口,我们要么必须实现所有东西,要么我们要定义我们要走多远。我不知道类型系统要跟踪它有多难。

当然,我给出的例子大多只适用于公共隐式接口,所以在实践中这可能就足够了。

Ian Griffiths 指出,问题可能在于混合类和接口。微软的 COM 技术实际上在两者之间有明显的区别:“如果你想在 COM 中使用一个对象,你必须通过一个接口来做。所以你总是能够自己实现。” 这在 VB 6 中变得非常透明,在 VB 6 中,COM 接口可以在后台生成。

这个问题在动态类型语言中不会出现。如果您想实现另一个类的接口,您只需要实现相同的方法,并在需要的地方使用该对象即可。您不需要实现每个方法,只需实现您所关注的特定交互中正在使用的方法即可。这是一种对测试非常有效的方案 - Smalltalker 曾一度将其称为 Imposter 模式。在 Java 中使用动态代理来做这类事情也很常见,尽管我觉得隐式接口实现更具沟通性。

这些重要吗?主要来说,这似乎是一个测试问题,如果您不能使用重新实现,那么插入 测试替身 要困难得多 - 如果超类想要与数据库建立真正的连接,那么子类化通常也无济于事。这很可能只是一个测试问题 - Robert Conley 告诉我,他经常使用 VB6 的重新实现功能进行测试,但在生产代码中从未发现需要它。