这些天我正在阅读Code Complete书,我已经通过了关于耦合级别(简单数据参数,简单对象,对象参数和语义耦合)的部分。语义是“最差”的类型:
当一个模块发生最阴险的耦合时 不使用其他模块的某些语法元素,而是使用某些语义 了解另一个模块的内部工作原理。
本书中的示例通常会导致运行时失败,并且是典型的错误代码,但今天我遇到的情况是我真的不确定如何对待。
首先我有一个课程,让我们称之为Provider
获取一些数据并返回List
Foo
个。
class Provider
{
public List<Foo> getFoos()
{
ArrayList<Foo> foos = createFoos();//some processing here where I create the objects
return foos;
}
}
消费类执行算法,处理Foo
,根据某些属性从List
合并或删除,这并不重要。该算法使用列表的头部进行所有处理。因此,有许多操作读取/删除/添加到头部。
(我刚刚意识到我可以让算法看起来像合并排序,在数组的一半上递归调用它,但这不能回答我的问题:))
我注意到我正在返回ArrayList
,因此我更改了提供类“getFoos
方法”以返回LinkedList
。 ArrayList
具有头部删除的O(n)复杂度,而LinkedList
具有恒定的复杂性。但它让我感到震惊,我可能会产生语义依赖。代码肯定适用于List
的两种实现,没有副作用,但性能也会降低。我写了这两个类,所以我很容易理解,但如果一个同事必须实现该算法,或者如果他使用Provider
作为另一个有利于随机访问的算法的源,那该怎么办呢。如果他不打扰内部,就像他不应该这样,我会搞砸他的算法性能。
声明返回LinkedList
的方法也是可能的,但是“接口程序”原则呢?
有没有办法处理这种情况,或者我的设计在根源上有缺陷?
答案 0 :(得分:4)
一般问题是,生产者如何以消费者喜欢的形式返回某些东西?通常,消费者需要在请求中包含首选项。例如
getFoos(randomOrLinked)
getFoosAsArrayList(), getFoosAsLinkedList()
getFoos(ArrayList::new)
getFoos(new ArrayList())
但是,制片人可能有权说,这对我来说太复杂了,我不在乎。我将返回一个适合大多数用例的表单,并且消费者需要正确处理它。如果我认为ArrayList
是最好的,我就会这样做。 (实际上,您可能有更好的选择 - 环形结构 - 适合两种用例)
当然,它应该有详细记录。或者您可以诚实地返回ArrayList
作为方法签名,只要您承诺它。不要过于担心&#34;界面&#34; - ArrayList是一个接口(在一般意义上),Iterable是一个接口,所以两者之间的List
接口有什么特别之处。
您的设计可能会有另一个批评 - 您返回一个可变数据结构,以便消费者可以直接修改。这比返回只读数据结构更不可取。如果可以,您应该返回基础数据的只读视图;视图的构造应该是便宜的。如果需要可变的消费者,消费者需要自己制作副本。
答案 1 :(得分:3)
你需要在某个地方做出妥协。在这种情况下,有两个对我有意义的妥协:
Provider
的所有消费者都将执行适合LinkedList
的操作,请坚持使用返回类型为List
的签名返回ArrayList
的实现(ArrayList
是一个很好的全面List
实现)。然后在调用方法中,将返回的List
包装在LinkedList
中:LinkedList<Foo> fooList = new LinkedList<>(provider.getFoos());
让调用者负责自己的优化。Provider
的所有消费者将以LinkedList
- 适当的方式使用它,那么只需将返回类型更改为LinkedList
- 或添加其他方法返回LinkedList
。我非常喜欢前者,但两者都有道理。
答案 2 :(得分:2)
您可以让来电者创建LinkedList
,而不是提供商 - 即更改
List<Foo> foos = provider.getFoos();
到
List<Foo> foos = new LinkedList<>(provider.getFoos());
然后,提供商返回的列表类型并不重要。缺点是仍然会创建ArrayList
,因此在效率和清洁度之间存在权衡。