一种方法,将可变数据结构声明为输出并实际返回不可变数据结构

Chi*_*ron 6 java oop inheritance design-patterns immutability

最近,我就这个问题进行了激烈的讨论.

让我们说我用Java创建了这个方法:

 public Set<String> getRich() {
    return ImmutableSet<String> ....;
 }
Run Code Online (Sandbox Code Playgroud)

每当我在拉动请求中看到它时,我会大喊并尝试解释它为什么是错误的.通过这样做,我通过承诺他们将得到一个Set来误导我的方法的消费者.这意味着他们可以删除或添加元素.javac很乐意编译它,但会抛出RuntimeException.除此之外,它违反了"利斯科夫替代原则".

就个人而言,我总是这样做:

public ImmutableSet<String> getRich() {
    return ImmutableSet<String> ....;
}
Run Code Online (Sandbox Code Playgroud)

这样,没有人会在脚下射击自己.

一种建议的方法是返回Iterable.我认为这很糟糕,因为该方法的使用者将失去HashSet,HashMap或其他任何东西(通过无法调用set.get(hash))的潜在能力.

另一种方法是 - 作为消费者 - 创建getRich()方法输出的副本.但是你如何确定消费者会这样做呢?

当然,你有那些坚持OOP设计原则的人说:"总是编程接口".对我来说 - 在这种特殊情况下 - 这是有史以来最糟糕的选择.

你会如何处理这个案子?

(非主题:这个案例是一个完美的例子,静态类型如何确保正确性,但永远不会确保正确性).

dca*_*tro 6

从技术上讲,声明返回类型Set<T>并不违反LSP.

用于Set<T>.Add明确声明实现可能会抛出的文档UnsupportedOperationException.这意味着客户有责任检查该集合是否可变.

现在,是否进行ImmutableSet<T>延伸Set<T>是一个不错的选择,这是值得商榷的.我个人认为这是一个非常糟糕的选择 - 客户端代码不应该充斥着try/catches来查明一个集合是否可变.此外,Set<T>甚至没有提供isMutable()让客户轻松执行检查的方法!为不可变集合设置单独的接口将是一个更清洁的设计.

我个人会解决这个设计缺陷,正如你的建议,将返回类型声明为ImmutableSet<T>.

问题的第二部分是你应该返回ImmutableSet<T>还是Iterable<T>:这实际上取决于你认为客户对你的API的期望.需要考虑的事项:

  • 您的实施可能会改变吗?您是否认为您需要更改业务逻辑并返回其他一些集合?如果是这样,请继续Iterable<T>;
  • 您的客户明确期望ImmutableSet<T>吗?如果没有,请继续Iterable<T>;
  • 否则,您可能希望遵循Robustness原则,该原则指出"输入类型中的逆变和输出类型中的协变".换句话说,为参数类型选择最抽象的类型,并为返回类型选择最具体的类型.