“代码到接口”被认为是好的做法。这样的代码易于进行单元测试并支持松散耦合。用户只知道接口,连接具体对象的责任在最顶层(这可以在一些初始化代码或框架的帮助下完成)。
我的问题是关于遵循代码到接口的做法:这是否意味着具体类永远不能声明其接口中不存在的任何公共方法?
否则,它将迫使用户依赖具体的实现。这将使此类方法难以进行单元测试;如果测试失败,确定它是由于调用者代码中的问题还是由于具体方法而失败将需要额外的努力。这也将打破依赖倒置原则。它将导致类型检查和向下转换,这被认为是不好的做法。
“代码到接口”被认为是好的做法。这样的代码易于进行单元测试并支持松散耦合。用户只知道接口,连接具体对象的责任在最顶层(这可以在一些初始化代码或框架的帮助下完成)。
我的问题是关于遵循代码到接口的做法:这是否意味着具体类永远不能声明其接口中不存在的任何公共方法?
否则,它将迫使用户依赖具体的实现。这将使此类方法难以进行单元测试;如果测试失败,确定它是由于调用者代码中的问题还是由于具体方法而失败将需要额外的努力。这也将打破依赖倒置原则。它将导致类型检查和向下转换,这被认为是不好的做法。
这是完全可以接受的,前提是新方法对类的操作并不重要,尤其是当有人认为它是超类或接口时它的功能。
ArrayList 提供了很好的例子。它具有让您管理其内部存储器的方法,例如ensureCapacity(int)
或trimToSize()
。如果您知道自己正在使用 ArrayList 并且需要更精确地分配内存,这些有时会很有帮助,但它们不是 ArrayList 的基本操作所必需的,特别是,它们不是拥有它所必需的作为一般列表操作。
实际上,接口本身可以通过这种方式添加新方法。考虑NavigableSet,它扩展了 Set。它添加了一大堆依赖于集合元素排序的方法(给我第一个、最后一个、从这里开始的子树等)。这些方法都没有在 Set 上定义,甚至元素是有序的这一事实也不是由 Set 契约定义的;但是 Set 方法都可以正常工作,无需额外的方法和排序。
“对接口编码”的建议是一个好的开始,但它有点过于笼统了。该建议的一个改进是“为您需要的最通用接口编写代码”。如果你不需要 ArrayLists 的方法(或者它的契约,比如它的随机访问性能),代码到 List;但是,如果您确实需要它们,那么请务必使用它们。
@yshavit 的第三段恰到好处。实现“不够”基本接口的扩展,例如public interface NavigableSet<E> extends SortedSet<E>
(which, BTW, extends Set<E> extends Collection<E> extends Iterable<E>
)。
困扰我的是他的第二段。为什么 API 的“非关键”方法没有出现在某些正在实现的接口中?在ArrayList
示例中,为什么不在接口中声明大小管理方法?也许ManagedSize
这将描述要实现的(和其他)类的清晰行为ArrayList
,以及它实现的其他几个接口(我的 JRE 来源说:)public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
。
使用这种方法,不需要决定哪些方法是“非关键的”,只会对一些客户端代码感到惊讶,这些代码依赖于诸如ensureSize
帮助避免在时间关键阶段重定位或trimToSize
释放过度过度操作之类的事情。从算法上讲,不需要进一步的增长。并不是说我将这种算法作为最佳实践来推广,但即使是非功能性的“行为管理”方法也应该得到他们的关注。
最后,虽然我同意“知道线条在哪里,但颜色随你所见”的观点,但它并没有给出实际的指导。这是这样的尝试:
List
, RandomAccess
, Cloneable
,Serializable
ManagedSize
上面的想法),然后同时实现它们#3 “不能”的原因会有所不同,但我希望它们在应用程序设计之外,例如我正在使用的 ORM 变得混乱,IDE 插件没有正确重构它,我的 DSL 转换器当一个类实现三个以上的接口时,我被迫使用失败......