因此,我知道编码到接口(使用接口作为变量的声明类型而不是其具体类型)是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的排序属性的任何场景。如何防止意外实现被使用?你在代码中强制它(通过抛弃界面),还是在文档中指定它?或许还有其他一些方法?
答案 0 :(得分:1)
非常非常好的问题。一个解决方案是:创建另一个界面!说一个扩展SortedMap但有一个getInsertionOrderIterator()方法或一个扩展Map&amp;的接口。有getOrderIterator()&amp; getInsertionOrderIterator()方法。
您可以编写一个包含LinkedHashMap&amp;的快速adapter类。 TreeMap作为后端数据结构。
答案 1 :(得分:1)
您应该对接口进行编码,但前提是它们的保证符合您的需求。在你的情况下,如果你只使用Set然后你说:我不想要重复,但我不关心订单。您也可以使用List并表示:我关心插入顺序,但不关心重复。甚至有一个SortedSet但它没有你想要的顺序。因此,在您的情况下,您不能通过其中一个接口替换LinkedHashSet而不违反Liskov替换原则。
所以我认为在你的情况下你应该坚持实现,直到你真的需要切换到另一个实现。使用现代IDE,重构不再那么难了,所以我不会做任何过早的优化 - YAGNI和KISS。
答案 2 :(得分:0)
您可以为任何一种方式制作参数。只要您和其他维护此代码的人知道Set
的特定实现可能会破坏应用程序或库的其余部分,那么编码到接口就可以了。但是,如果不是这样,那么您应该使用特定的实现。
编码到界面的目的是为了给您灵活性,不会破坏您的应用程序。以JDBC为例。如果您使用错误的驱动程序,它将破坏您的程序,类似于您在此处描述的方式。但是,如果让我们说Oracle决定将行为放在他们的JDBC驱动程序中,巧妙地破坏了编写到JDBC规范的代码而不是特定的Oracle驱动程序代码,那么你必须选择。
没有剪切和干燥,“这总是正确的”答案类型。