【问题标题】:Avoiding semantic coupling with Java Collection interfaces避免与 Java 集合接口的语义耦合
【发布时间】:2015-05-13 22:33:01
【问题描述】:

这些天我正在阅读 Code Complete 这本书,并且我已经通过了有关耦合级别(简单数据参数、简单对象、对象参数和语义耦合)的部分。语义是“最差”的一种:

最隐蔽的耦合发生在一个模块 不使用另一个模块的一些句法元素,而是使用一些语义 了解另一个模块的内部工作原理。

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

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

class Provider
{
   public List<Foo> getFoos()
   {
      ArrayList<Foo> foos = createFoos();//some processing here where I create the objects
      return foos;
   }
}

一个消费类执行一个算法,处理Foo's,根据一些属性合并或删除List,这并不重要。该算法使用列表的头部完成所有处理。所以有很多操作读取/删除/添加到头部。

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

我注意到我返回了一个ArrayList,所以我将提供类的getFoos 方法更改为返回一个LinkedListArrayList 具有 O(n) 复杂度和头部移除,而 LinkedList 具有恒定复杂度。但后来让我感到震惊的是,我可能会产生语义依赖。该代码当然可以与List 的两种实现一起使用,没有副作用,但性能也会降低。而且我编写了这两个类,以便我可以轻松理解,但是如果一个同事必须实现该算法,或者如果他使用Provider 作为另一个有利于随机访问的算法的源,该怎么办。如果他不关心内部结构,就像他不应该那样,我会搞砸他的算法性能。

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

有什么办法可以处理这种情况,还是我的设计存在根本缺陷?

【问题讨论】:

  • 老实说——这不是一个糟糕的设计; LinkedList 只是一个糟糕的List。我见过的大多数代码都假设它正在处理RandomAccessList,这并不是一个不合理的假设。
  • 同意@LouisWasserman - 几乎不应该使用LinkedList。您可以将所有 LinkedList 替换为 ArrayDeque。

标签: java algorithm oop design-patterns


【解决方案1】:

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

  1. 作为标志 - getFoos(randomOrLinked)
  2. 不同的方法 - getFoosAsArrayList(), getFoosAsLinkedList()
  3. 传递一个创建所需列表的函数 - getFoos(ArrayList::new)
  4. 或传递所需的输出列表 - getFoos(new ArrayList())

但是,制作人可能有权说,这对我来说太复杂了,我不在乎。我会返回一个适合大多数用例的表单,消费者需要妥善处理。如果我认为ArrayList 是最好的,我会这样做。 (实际上,您可能有更好的选择 - 环形结构 - 适合考虑的两个用例)

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

您的设计可能还有其他批评 - 您返回一个可变数据结构,以便消费者可以直接修改。这比返回只读数据结构更不可取。如果可以,您应该返回基础数据的只读视图;视图的构建应该是便宜的。如果需要一个可变副本,消费者需要自己制作副本。

【讨论】:

  • 第三个和第四个选项在我写作的时候甚至都没有想到,尽管我以前都使用过。哇!
  • 很难从所有好的答案中做出选择,但我相信这是最详细的一个。感谢所有人,这是非常有建设性的:)。关于不变性的观点!
【解决方案2】:

你需要在某个地方做出妥协。在这种情况下,我认为有两个折衷方案:

  1. 如果您不知道Provider 的所有使用者都将执行适合LinkedList 的操作,那么请坚持使用返回类型为List 的签名,并使用返回ArrayList 的实现( ArrayList 是一个很好的全方位 List 实现)。然后在调用方法中,将返回的List 包装在LinkedList 中:LinkedList&lt;Foo&gt; fooList = new LinkedList&lt;&gt;(provider.getFoos()); 让调用者负责自己的优化。
  2. 如果您知道Provider 的所有使用者都将以LinkedList 适当的方式使用它,那么只需将返回类型更改为LinkedList — 或添加另一个返回LinkedList 的方法.

我非常喜欢前者,但两者都有意义。

【讨论】:

    【解决方案3】:

    您可以让调用者创建LinkedList,而不是提供者 - 即更改

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

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

    那么提供者返回什么样的列表并不重要。缺点是仍然会创建ArrayList,因此需要在效率和清洁度之间进行权衡。

    【讨论】:

    • 给我点赞:这是一件很常见的事情,而且大部分时间都很合理。
    猜你喜欢
    • 2011-08-18
    • 2011-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多