为什么坚持接口的所有实现都扩展基类?

我只是在GitHub上查看Java Hamcrest代码,并注意到他们采用了一种似乎不直观和尴尬的策略,但它让我怀疑我是否遗漏了什么。

我注意到在HamCrest API中有一个接口匹配器和一个抽象类BaseMatcher。Matcher 接口声明此方法,并使用以下 javadoc:

    /**
     * This method simply acts a friendly reminder not to implement Matcher directly and
     * instead extend BaseMatcher. It's easy to ignore JavaDoc, but a bit harder to ignore
     * compile errors .
     *
     * @see Matcher for reasons why.
     * @see BaseMatcher
     * @deprecated to make
     */
    @Deprecated
    void _dont_implement_Matcher___instead_extend_BaseMatcher_();

然后在BaseMatcher中,此方法实现如下:

    /**
     * @see Matcher#_dont_implement_Matcher___instead_extend_BaseMatcher_()
     */
    @Override
    @Deprecated
    public final void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
        // See Matcher interface for an explanation of this method.
    }

诚然,这既有效又可爱(而且非常尴尬)。但是,如果意图是每个实现 Matcher 的类也扩展 BaseMatcher,那么为什么要使用接口呢?为什么不首先让 Matcher 成为一个抽象类,并让所有其他匹配器扩展它呢?像Hamcrest那样做这件事有什么好处吗?或者这是不良做法的一个很好的例子?

编辑

一些很好的答案,但为了寻找更多细节,我提供了赏金。我认为向后/二进制兼容性问题是最好的答案。但是,我希望看到更多关于兼容性的问题,最好是使用一些代码示例(最好是Java)。此外,“向后”兼容性和“二进制”兼容性之间是否存在细微差别?

进一步编辑

2014年1月7日 -- pigroxalot在下面提供了答案,并链接到HamCrest作者在Reddit上的评论。我鼓励大家阅读它,如果你觉得它内容丰富,请投票支持猪侠的答案。

进一步编辑

2017年12月12日 -- pigroxalot的答案不知何故被删除了,不知道这是怎么回事。太糟糕了...这个简单的链接非常翔实。


答案 1

从 2006 年 12 月开始(首次签入后约 9 个月),有此条目:git log

添加了所有匹配器都应扩展的抽象 BaseMatcher 类。这允许随着 Matcher 接口的发展实现未来的 API 兼容性 [原文如此]。

我还没有试图弄清楚细节。但是,随着系统的发展,保持兼容性和连续性是一个难题。这确实意味着,有时你最终会得到一个设计,如果你从头开始设计整个东西,你永远不会,永远不会,永远不会创造。


答案 2

但是,如果意图是每个实现 Matcher 的类也扩展 BaseMatcher,那么为什么要使用接口呢?

这并不完全是意图。抽象基类和接口从 OOP 的角度来看提供了完全不同的“协定”。

接口是一种通信协定。接口由类实现,以向世界表示它遵守某些通信标准,并将提供特定类型的结果以响应具有特定参数的特定调用。

抽象基类是一个实现协定。抽象基类由类继承,以提供基类需要但留给实现者提供的功能。

在这种情况下,两者重叠,但这仅仅是一个方便的问题 - 接口是你需要实现的,抽象类是为了使实现接口更容易 - 没有任何要求使用该基类来提供接口,它只是在那里使它减少这样做的工作量。在为自己的目的扩展基类、不关心接口协定或实现实现相同接口的自定义类方面,您绝不受任何限制。

给定的做法实际上在老式的 COM/OLE 代码和其他促进进程间通信 (IPC) 的框架中相当普遍,其中实现与接口分离变得至关重要 - 这正是这里所做的。


推荐