避免与Java集合接口的语义耦合

避免与Java集合接口的语义耦合,java,algorithm,oop,design-patterns,Java,Algorithm,Oop,Design Patterns,这些天,我正在阅读《代码全集》,我已经通过了关于耦合级别(简单数据参数、简单对象、对象参数和语义耦合)的部分。语义是“最差”的类型: 当一个模块 不使用其他模块的某些语法元素,而是使用某些语义元素 了解其他模块的内部工作原理 书中的例子通常会导致运行时失败,并且是典型的错误代码,但是今天我遇到了一个我真的不知道如何处理的情况 首先,我有一个类,我们称它为Provider获取一些数据并返回Foo的列表 class Provider { public List<Foo> getFo

这些天,我正在阅读《代码全集》,我已经通过了关于耦合级别(简单数据参数、简单对象、对象参数和语义耦合)的部分。语义是“最差”的类型:

当一个模块 不使用其他模块的某些语法元素,而是使用某些语义元素 了解其他模块的内部工作原理

书中的例子通常会导致运行时失败,并且是典型的错误代码,但是今天我遇到了一个我真的不知道如何处理的情况

首先,我有一个类,我们称它为
Provider
获取一些数据并返回
Foo
列表

class Provider
{
   public List<Foo> getFoos()
   {
      ArrayList<Foo> foos = createFoos();//some processing here where I create the objects
      return foos;
   }
}
类提供程序
{
公共列表getFoos()
{
ArrayList foos=createFoos();//我在这里创建对象的一些处理
返回foos;
}
}
消费类执行一个算法,处理
Foo
,根据一些属性合并或从
列表中删除,这并不重要。该算法以列表的开头完成所有处理。因此,有很多操作读取/删除/添加到头部

(我刚刚意识到我可以让算法看起来像合并排序,递归地在数组的一半上调用它,但这并不能回答我的问题:)

我注意到我正在返回一个
ArrayList
,因此我将提供类'
getFoos
方法更改为返回一个
LinkedList
ArrayList
的头移除复杂度为O(n),而
LinkedList
的复杂度为常数。但我突然想到,我可能是在做语义依赖。该代码肯定会与
列表的两种实现一起工作,没有副作用,但性能也会降低。我写了这两个类,这样我就很容易理解了,但是如果一个同事必须实现这个算法,或者如果他使用
提供程序
作为另一个支持随机访问的算法的源呢。如果他不去处理内部问题,就像他不应该做的那样,我会把他的算法性能搞砸

声明返回
LinkedList
的方法也是可能的,但是“程序到接口”原则呢


有没有办法处理这种情况,或者我的设计在根目录中有缺陷?

您可以让调用者创建
链接列表,而不是提供程序-即更改

List<Foo> foos = provider.getFoos();
List foos=provider.getFoos();

listfoos=newlinkedlist(provider.getFoos());

那么,提供者返回什么类型的列表就无关紧要了。缺点是仍然会创建
阵列列表,因此在效率和清洁度之间需要权衡。

您需要在某个地方做出折衷。在这种情况下,有两种妥协对我来说是有意义的:

  • 如果您不知道您的
    提供者的所有使用者将执行适合
    链接列表的操作,那么请使用返回类型为
    列表的签名以及返回
    数组列表的实现(
    ArrayList
    是一个很好的全面的
    List
    实现)。然后在调用方法中,将返回的
    List
    包装在
    LinkedList
    中:
    LinkedList傻瓜列表=新的LinkedList(provider.getFoos());
    让调用方负责自己的优化
  • 如果您知道
    提供商的所有消费者将以
    链接列表
    的适当方式使用它,那么只需将返回类型更改为
    链接列表
    ——或者添加另一个返回
    链接列表
    的方法即可

  • 我非常喜欢前者,但两者都有道理。

    一般的问题是,生产者如何以消费者喜欢的形式返回商品?通常消费者需要在请求中包含偏好。例如

  • 作为标志-
    getFoos(随机链接)
  • 不同的方法-
    getFoosAsArrayList(),getFoosAsLinkedList()
  • 传递创建所需列表的函数-
    getFoos(ArrayList::new)
  • 或者传递所需的输出列表-
    getFoos(newarraylist())
  • 但是,制作人可能有权说,这对我来说太复杂了,我不在乎。我将返回一个适合大多数用例的表单,消费者需要正确处理它。如果我认为
    ArrayList
    是最好的,我就这样做。(实际上,您可能会有一个更好的选择-一个环形结构-适合考虑中的两个用例)

    当然,它应该有很好的文档记录。或者你可以诚实地返回
    ArrayList
    作为方法签名,只要你愿意。不要太担心“接口”-ArrayList是一个接口(一般意义上),Iterable是一个接口,那么介于两者之间的
    List
    接口有什么特别之处呢


    对您的设计可能还有另一种批评-您返回一个可变的数据结构,以便使用者可以直接修改。这不如返回只读数据结构。如果可以,您应该返回基础数据的只读视图;视图的构造应该便宜。使用者需要自己做如果它需要一个可变的,请复制。

    说实话——这不是一个坏的设计;
    链接列表
    只是一个坏的
    列表
    。我见过的大多数代码都假设它处理的是
    随机访问
    列表
    ,这并不是一个不合理的假设。同意@LouisWasserman-链接列表应该几乎永远不会出现你可以用ArrayDeque替换你所有的LinkedList。我的投票:这是一件非常常见的事情,大多数情况下都是合理的
    List<Foo> foos = new LinkedList<>(provider.getFoos());