避免与 Java 集合接口的语义耦合
Avoiding semantic coupling with Java Collection interfaces
这几天我正在阅读《代码大全》这本书,并且已经通过了关于耦合级别的部分(简单数据参数、简单对象、对象参数和语义耦合)。语义是 "worst" 种类:
The most insidious kind of coupling occurs when one module
makes use not of some syntactic element ofanother module but of some semantic
knowledge of another module’s inner workings.
书上的例子通常会导致运行次失败,是典型的烂代码,但是今天我遇到了一种情况,我真的不知道该如何处理。
首先我有一个 class,我们称它为 Provider
获取一些数据并 returning List
of Foo
。
class Provider
{
public List<Foo> getFoos()
{
ArrayList<Foo> foos = createFoos();//some processing here where I create the objects
return foos;
}
}
一个消费class执行算法,处理Foo
的,根据一些属性合并或从List
中删除,不是很重要。该算法使用列表的头部进行所有处理。所以有很多操作reading/removing/adding到头
(我刚刚意识到我可以让算法看起来像合并排序,递归地在数组的一半上调用它,但这不能回答我的问题:))
我注意到我正在 return 一个 ArrayList
所以我将提供 class' getFoos
方法更改为 return 一个 LinkedList
. ArrayList
的复杂度为 O(n),移除头部,而 LinkedList
的复杂度为常量。但后来我突然意识到我可能会产生语义依赖。该代码肯定会与 List
的两种实现一起工作,没有副作用,但性能也会降低。我写了两个 classes 所以我很容易理解,但是如果同事必须实现算法,或者如果他使用 Provider
作为另一种有利于随机访问的算法的来源怎么办。如果他不关心内部结构,就像他不应该关心的那样,我会搞砸他的算法性能。
将方法声明为returnLinkedList
也是可以的,但是"program to interface"原理呢?
有什么办法可以处理这种情况,还是我的设计有根源上的缺陷?
您可以让调用者创建 LinkedList
,而不是提供者 - 即更改
List<Foo> foos = provider.getFoos();
到
List<Foo> foos = new LinkedList<>(provider.getFoos());
那么提供者的列表类型并不重要returns。缺点是仍然会创建一个 ArrayList
,因此需要在效率和清洁之间进行权衡。
你需要在某个地方做出妥协。在这种情况下,有两个对我来说有意义的折衷方案:
- 如果您不知道
Provider
的所有消费者都将执行适合 LinkedList
的操作,那么请坚持使用 return 类型的签名 List
实现 returns ArrayList
(ArrayList
是一个很好的全能 List
实现)。然后在调用方法中,将 returned List
包装在 LinkedList
中: LinkedList<Foo> fooList = new LinkedList<>(provider.getFoos());
让调用者负责自己的优化。
- 如果您知道
Provider
的所有消费者都将以 LinkedList
适当的方式使用它,那么只需将 return 类型更改为 LinkedList
-- 或者添加另一个方法 return 是 LinkedList
.
我非常喜欢前者,但两者都有道理。
普遍的问题是,生产者如何return 以消费者喜欢的形式提供东西?通常消费者需要在请求中包含偏好。例如
- 作为旗帜 -
getFoos(randomOrLinked)
- 不同的方法 -
getFoosAsArrayList(), getFoosAsLinkedList()
- 传递创建所需列表的函数 -
getFoos(ArrayList::new)
- 或传递所需的输出列表 -
getFoos(new ArrayList())
但是,制作人可能有权利说,这对我来说太复杂了,我不在乎。我会 return 一个适合大多数用例的表单,消费者需要正确处理它。如果我认为 ArrayList
是最好的,我就会这样做。 (实际上,您可能有更好的选择——环形结构——同时适合所考虑的两个用例)
当然要有据可查。或者,您可以诚实地将 return ArrayList
作为方法签名,只要您愿意。不要太担心 "interface" - ArrayList 是一个接口(一般意义上),Iterable 是一个接口,那么介于两者之间的 List
接口有什么特别之处。
您的设计还有另一个批评 - 您 return 一个可变数据结构,以便用户可以直接修改。这比 return 只读数据结构更不可取。如果可以,您应该 return 底层数据的只读视图;视图的构造应该是廉价的。如果消费者需要可变的副本,则需要自己做副本。
这几天我正在阅读《代码大全》这本书,并且已经通过了关于耦合级别的部分(简单数据参数、简单对象、对象参数和语义耦合)。语义是 "worst" 种类:
The most insidious kind of coupling occurs when one module makes use not of some syntactic element ofanother module but of some semantic knowledge of another module’s inner workings.
书上的例子通常会导致运行次失败,是典型的烂代码,但是今天我遇到了一种情况,我真的不知道该如何处理。
首先我有一个 class,我们称它为 Provider
获取一些数据并 returning List
of Foo
。
class Provider
{
public List<Foo> getFoos()
{
ArrayList<Foo> foos = createFoos();//some processing here where I create the objects
return foos;
}
}
一个消费class执行算法,处理Foo
的,根据一些属性合并或从List
中删除,不是很重要。该算法使用列表的头部进行所有处理。所以有很多操作reading/removing/adding到头
(我刚刚意识到我可以让算法看起来像合并排序,递归地在数组的一半上调用它,但这不能回答我的问题:))
我注意到我正在 return 一个 ArrayList
所以我将提供 class' getFoos
方法更改为 return 一个 LinkedList
. ArrayList
的复杂度为 O(n),移除头部,而 LinkedList
的复杂度为常量。但后来我突然意识到我可能会产生语义依赖。该代码肯定会与 List
的两种实现一起工作,没有副作用,但性能也会降低。我写了两个 classes 所以我很容易理解,但是如果同事必须实现算法,或者如果他使用 Provider
作为另一种有利于随机访问的算法的来源怎么办。如果他不关心内部结构,就像他不应该关心的那样,我会搞砸他的算法性能。
将方法声明为returnLinkedList
也是可以的,但是"program to interface"原理呢?
有什么办法可以处理这种情况,还是我的设计有根源上的缺陷?
您可以让调用者创建 LinkedList
,而不是提供者 - 即更改
List<Foo> foos = provider.getFoos();
到
List<Foo> foos = new LinkedList<>(provider.getFoos());
那么提供者的列表类型并不重要returns。缺点是仍然会创建一个 ArrayList
,因此需要在效率和清洁之间进行权衡。
你需要在某个地方做出妥协。在这种情况下,有两个对我来说有意义的折衷方案:
- 如果您不知道
Provider
的所有消费者都将执行适合LinkedList
的操作,那么请坚持使用 return 类型的签名List
实现 returnsArrayList
(ArrayList
是一个很好的全能List
实现)。然后在调用方法中,将 returnedList
包装在LinkedList
中:LinkedList<Foo> fooList = new LinkedList<>(provider.getFoos());
让调用者负责自己的优化。 - 如果您知道
Provider
的所有消费者都将以LinkedList
适当的方式使用它,那么只需将 return 类型更改为LinkedList
-- 或者添加另一个方法 return 是LinkedList
.
我非常喜欢前者,但两者都有道理。
普遍的问题是,生产者如何return 以消费者喜欢的形式提供东西?通常消费者需要在请求中包含偏好。例如
- 作为旗帜 -
getFoos(randomOrLinked)
- 不同的方法 -
getFoosAsArrayList(), getFoosAsLinkedList()
- 传递创建所需列表的函数 -
getFoos(ArrayList::new)
- 或传递所需的输出列表 -
getFoos(new ArrayList())
但是,制作人可能有权利说,这对我来说太复杂了,我不在乎。我会 return 一个适合大多数用例的表单,消费者需要正确处理它。如果我认为 ArrayList
是最好的,我就会这样做。 (实际上,您可能有更好的选择——环形结构——同时适合所考虑的两个用例)
当然要有据可查。或者,您可以诚实地将 return ArrayList
作为方法签名,只要您愿意。不要太担心 "interface" - ArrayList 是一个接口(一般意义上),Iterable 是一个接口,那么介于两者之间的 List
接口有什么特别之处。
您的设计还有另一个批评 - 您 return 一个可变数据结构,以便用户可以直接修改。这比 return 只读数据结构更不可取。如果可以,您应该 return 底层数据的只读视图;视图的构造应该是廉价的。如果消费者需要可变的副本,则需要自己做副本。