具体实现是否应该提供它实现的接口中不存在的任何 public API?

Should concrete implementation provide any public API not present in the interface it implements?

"接口代码"被认为是好的做法。这样的代码很容易进行单元测试,并且可以实现松散耦合。用户只知道接口,连接具体对象的责任在最顶层(这可以在一些初始化代码中或在框架的帮助下完成)。

我的问题是关于遵循 代码到接口 的做法:这是否意味着具体 class 永远不能声明任何 public 方法没有出现在它的界面中?

否则会强制用户依赖具体实现。这将使此类方法难以进行单元测试;如果测试失败,确定它失败是由于调用者代码中的问题还是由于具体方法将需要额外的努力。这也将打破依赖倒置原则。它将导致类型检查和向下转换,这被认为是不好的做法。

这是完全可以接受的前提是新方法对于 class 的操作并不重要,尤其是当有人想到它作为 superclass 或接口。

ArrayList 提供了很好的示例。它有一些方法可以让你管理它的内部内存,比如 ensureCapacity(int) or trimToSize()。如果您知道您正在使用 ArrayList 并且需要更精确地分配内存,那么这些有时会很有帮助,但是 ArrayList 的基本操作不需要它们,特别是拥有它不需要它们作为一般列表操作。

事实上,接口本身可以通过这种方式添加新的方法。考虑 NavigableSet,它扩展了 Set。它添加了一大堆依赖集合元素排序的方法(给我第一个、最后一个、从这里开始的子树等)。 None 这些方法是在 Set 上定义的,甚至元素是有序的这一事实也不是由 Set 契约定义的;但是 Set 方法在没有额外方法和排序的情况下都可以正常工作。

给 "code to the interface" 的建议是一个好的开始,但有点过于笼统了。对该建议的改进是,"code to the most general interface that you need." 如果您不需要 ArrayLists 的方法(或其契约,例如其随机访问性能),请编码为 List;但如果你确实需要它们,那么一定要使用它们。

@yshavit 的第三段说得很对。实现 "not enough" 基本接口的扩展,例如 public interface NavigableSet<E> extends SortedSet<E>(顺便说一句,extends Set<E> extends Collection<E> extends Iterable<E>)。

困扰我的是他的第二段。为什么 API 的 "non-crucial" 方法未在某些接口中实现?在 ArrayList 示例中,为什么不在接口中声明大小管理方法?也许 ManagedSize 可以描述 ArrayList(和其他)classes 实现的明确行为,以及它实现的其他几个接口(我的 JRE 来源说:public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable) .

使用这种方法,无需决定哪些方法 "non-crucial," 只会让一些客户端代码感到惊讶,这些代码依赖于 ensureSize 之类的东西来帮助避免在一段时间内重定位 -关键阶段,或 trimToSize 在算法上知道不需要进一步增长时释放过度的过度动作。并不是说我将此类算法作为最佳实践来推广,但即使是非功能性的 "behavior management" 方法也值得一提。

最后,虽然我同意 "Know Where the Lines Are, and yet Color As You See Fit" 的观点,但它没有提供实际指导。这是这样的尝试:

  1. 总是 从对接口进行编码开始,即。所有具体的 public 方法都应该在接口中声明:
    1. 根据需要使用多个接口
    2. 每个接口都应将实现的 API 划分为连贯的非重叠方面,例如ListRandomAccessCloneableSerializable
    3. 倾向于从更大范围的接口开始,并随着设计的发展将它们分解(在编码 Waterfall 之前,或者随着代码的发展 Agile);界面是更容易重构的设计工件之一。
  2. 如果您要实现的给定接口是 "insufficient":
    1. 扩展基本接口并添加您需要的方法,然后实现该方法,
    2. 仅使用额外的方法创建一个扩充接口(如上面的 ManagedSize 想法),然后同时实现它们
  3. 只有当你发现自己做不到的时候,才放松只有 尽可能多的规则使事情正常进行(通常,这将是一个实验性的试错 "does it work, yet?" 周期)。

#3 的 "can't" 的原因会有所不同,但我希望它们在应用程序设计之外,例如我正在使用的 ORM 变得混乱,IDE 插件没有正确重构它,当 class 实现三个以上的接口时,我被迫使用的 DSL 翻译器失败了……