1

因此,我知道对接口进行编码(使用接口作为变量的声明类型而不是其具体类型)在 OO 代码中是一种很好的做法,原因有很多。这在 Java 集合中很常见。那么,当只有该接口的某些实现提供正确的行为时,在您的程序中引用一个接口仍然是一件好事吗?

例如,我有一个 Java 程序。在那个程序中,我有多组对象。我选择使用 Set,因为我不想要重复的元素。但是,我想要一个列表的排序属性(即维护插入顺序)。因此,我使用 LinkedHashSet 作为具体的 Set 类型。这些集合的用途之一是计算涉及集合中包含的对象的原始字段的点积,例如(简化一点):

double dot(LinkedHashSet<E> set, double[] array) {
    double sum = 0.0;
    int i = 0;
    for(E element : set) {
        sum += (element.getValue()*array[i]);
    }
    return sum;
}

此方法的结果取决于集合的迭代顺序,因此某些 Set 实现,主要是 HashSet,将给出不正确/意外的结果。目前,我在整个程序中使用 LinkedHashSet 作为声明的类型,而不是 Set,以确保正确的行为。然而,这在风格上感觉很糟糕。在这里做什么是正确的?在这种情况下可以使用具体类型吗?或者也许我应该使用 Set 作为类型,然后在文档中说明哪些实现将/不会产生正确的行为?我正在寻找更多的一般输入,而不是特定于上述场景的任何内容。特别是,这应该适用于您使用 LinkedHashSet 或 TreeSet 的排序属性的任何场景。您如何防止使用意外的实现?你是在代码中强制它(通过放弃接口),还是在文档中指定它?或者也许其他一些方法?

4

3 回答 3

1

确实,您应该对接口进行编码,但前提是它们做出的保证符合您的需求。在您的情况下,如果您只使用 Set 那么您是在说:我不想要重复,但我不在乎顺序。您也可以使用 List 并表示:我关心插入顺序,但不关心重复。甚至还有一个 SortedSet,但它没有您想要的排序。因此,在您的情况下,您不能在不违反 Liskov 替换原则的情况下用其接口之一替换 LinkedHashSet。

所以我认为,在你的情况下,你应该坚持实施,直到你真的需要切换到另一个实施。使用现代 IDE,重构不再那么难,所以我会避免做任何过早的优化——YAGNI 和 KISS。

于 2013-11-03T02:07:36.117 回答
1

非常非常好的问题。一种解决方案是:制作另一个界面!说一个扩展 SortedMap 但具有 getInsertionOrderIterator() 方法或扩展 Map & 具有 getOrderIterator() 和 getInsertionOrderIterator() 方法的接口。

您可以编写一个包含 LinkedHashMap 和 TreeMap 作为后端数据结构的快速适配器类。

于 2013-11-03T01:19:16.100 回答
0

你可以为任何一种方式提出论据。只要您和维护此代码的其他人知道特定实现Set可能会破坏应用程序或库的其余部分,那么对接口进行编码就可以了。但是,如果这不是真的,那么您应该使用特定的实现。

对界面进行编码的目的是为您提供不会破坏您的应用程序的灵活性。以 JDBC 为例。如果您使用错误的驱动程序,它将破坏您的程序,类似于您在此处描述的方式。但是,如果假设 Oracle 决定将行为放入他们的 JDBC 驱动程序中,该行为巧妙地破坏了写入 JDBC 规范的代码,而不是特定的 Oracle 驱动程序代码,那么您必须做出选择。

没有一刀切,“这永远是正确的”类型的答案。

于 2013-11-03T01:17:23.993 回答