【问题标题】:Correct implementation for property of all objects that are equal正确实现所有相等对象的属性
【发布时间】:2019-05-12 08:13:06
【问题描述】:

问题

考虑一个图的实现,SampleGraph<N>。 考虑一个图节点的实现,Node extends N,正确覆盖 hashCodeequals 以反映两个节点之间的逻辑相等。

现在,假设我们要向节点添加一些属性p。这样的属性绑定到节点的逻辑实例,即对于Node n1, n2n1.equals(n2) 意味着 p(n1) = p(n2)

如果我只是将属性添加为 Node 类的字段,我就遇到过这种情况:

  • 我定义Node n1, n2 使得n1.equals(n2)n1 != n2
  • 我将n1n2 添加到图表中:在插入逻辑节点时n1,在插入边期间引用节点时n2。该图存储了这两个实例。
  • 稍后,我从图中检索节点(返回n1)并将其上的属性 p 设置为某个值。稍后,我遍历图形的所有边,并从其中之一检索节点(返回n2)。 p 属性未设置,导致我的模型出现逻辑错误。

总而言之,当前行为

graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph stores n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n2 is returned

问题

以下所有陈述对我来说似乎都是合理的。没有一个比其他的更能说服我,所以我正在寻找基于软件工程规范的最佳实践指南。

S1 - 图表实现很差。添加节点后,图形应始终在内部检查它是否具有相同节点的实例(equals 评估为真)记忆。如果是这样,则此类实例应始终是图表使用的唯一参考。

graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph internally checks that n2.equals(n1), doesn't store n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n1 is returned

S2 - 假设图形在 S1 中的行为是错误的。程序员应注意始终将相同的节点实例传递给图形。

graph.addNode(n1) // n1 is added
graph.addEdge(n1,nOther) // the programmer uses n1 every time he refers to the node
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n1 is returned

S3 - 属性没有以正确的方式实现。它应该是类Node 之外的信息。一个集合,例如 HashMap<N, Property>,可以正常工作,将不同的实例视为基于 hashCode 的同一个对象。

HashMap<N, Property> properties;

graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph stores n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n2 is returned

// get the property. Difference in instances does not matter
properties.get(n1)
properties.get(n2) //same property is returned

S4 - 与 S3 相同,但我们可以将实现隐藏在 Node 中,这样:

class Node {
  private static HashMap<N, Property> properties;

  public Property getProperty() {
    return properties.get(this);
  }
}

编辑:在Stephen Canswer 之后为当前行为和暂定解决方案添加了代码 sn-ps。澄清一下,整个示例来自使用来自开源 Java 项目的真实图形数据结构。

【问题讨论】:

  • S4 对我来说似乎不是一个好的选择。 S3 可能非常复杂,只有在您需要解耦时才值得(例如,如果节点的数据可能完全不同)。 S2 可以,但可能会导致某些地方不会发生(开发人员的错误等)。因此,IMO S1 将是可行的方法,而 S2 可以对其进行补充,以免创建不必要的实例。
  • 感谢您的意见。我不确定我是否理解您对 S3 的评论,因为每个逻辑节点的属性应该不同。
  • 嗯,S4 不是一个好的选择,部分原因是静态地图。如果 S3 与其他持有地图的类相似,您仍然会遇到同样的问题。因此,建议使用适当的缓存/存储库。此外,每当您加载或删除可能变得非常复杂的节点时,您都​​必须处理加载和驱逐属性。不正确地这样做可能会导致查找失败(也就是缓存丢失)或如果节点刚刚从图中删除(因为至少缓存/repo 仍会保留引用),则节点不会被垃圾收集。

标签: java oop


【解决方案1】:

似乎 S1 最有意义。一些 Graph 实现在内部使用Set&lt;Node&gt;(或一些等效的)来存储节点。当然,使用像Set 这样的结构将确保没有重复的Nodes,其中Node n1Node n2 当且仅当n1.equals(n2) 被认为是重复的。当然,Node 的实现应该确保在比较两个实例时(即实现equals()hashCode() 时)考虑所有相关属性。

其他语句的一些问题:

S2 虽然可能是合理的,但会产生一个实现,其中负担落在客户身上来理解和防范内部 Graph 实现的潜在缺陷,这是设计不良的明显标志Graph 对象的 API。

S3 和 S4 看起来都很奇怪,虽然我可能不太了解情况。一般来说,如果Node 保存了一些数据,那么在Node 类中定义一个成员变量来反映这一点似乎是完全合理的。为什么要区别对待这个额外的属性?

【讨论】:

  • 感谢您的回答。我对 S3 和 S4 的想法完全相同,static 听起来很有趣。由于我使用的是外部提供的类(我写了Node,但不是SampleGraph),我不确定错误出在哪里,所以我试图考虑可能的修复方法。
  • Why should this extra property be treated any differently? 只是为了解决问题,用equals 的相同逻辑将不同的实例合并到同一个对象中。看起来很糟糕,工作正常。 S4 是我最终实现的(尽管我在这里学习这不是最佳实践)。
【解决方案2】:

在我看来,它归结为在具有强抽象或弱抽象的 API 之间进行选择。

  • 如果您选择强抽象,API 将隐藏 Node 对象具有标识的事实,并在将它们添加到 SimpleGraph 时将它们规范化。

  • 如果您选择弱抽象,API 将假定 Node 对象具有标识,并且在将它们添加到 SimpleGraph 之前由调用者对其进行规范化。

    李>

这两种方法导致不同的 API 合约,需要不同的实现策略。该选择可能会对性能产生影响......如果这很重要的话。

然后是 API 设计的更精细的细节,这些细节可能与您的图表特定用例相匹配,也可能不匹配。

关键是您需要做出选择。

(这有点像决定使用集合 List 接口及其干净的模型,而不是实现自己的链表数据结构,以便您可以有效地将 2 个列表“拼接”在一起。任何一种方法 都可以 是否正确,具体取决于您的应用程序的要求。)

请注意,您通常可以做出选择,尽管选择可能很困难。例如,如果您使用的是别人设计的 API:

  • 您可以选择按原样使用它。 (吸吮它!)
  • 您可以选择尝试影响设计。 (祝你好运!)
  • 您可以选择切换到不同的API;即不同的供应商。
  • 您可以选择分叉 API 并根据您自己的要求(或偏好,如果这是关于此的内容)进行调整
  • 您可以选择从头开始设计和实现自己的 API。

如果您真的别无选择,那么这个问题没有实际意义。只需使用 API。


如果这是一个开源 API,那么您可能无法选择让设计人员对其进行更改。重大的 API 大修倾向于为其他人创造大量工作;即依赖 API 的许多 other 项目。负责任的 API 设计师/设计团队会考虑到这一点。否则他们会发现他们失去了相关性,因为他们的 API 因不稳定而闻名。

所以......如果你的目标是影响现有的开源 API 设计......'因为你认为他们做错了(对于不正确的一些定义)......你最好“分叉” API 和处理后果。


最后,如果您正在寻找“最佳实践”建议,请注意there are no best practices。这不仅仅是一个哲学问题。这就是为什么如果你去寻求/寻找“最佳实践”建议,然后遵循它,你会被搞砸的原因。


作为脚注:您有没有想过为什么 Java 和 Android 标准类库不提供任何通用图形 API 或实现?为什么他们花了这么长时间才出现在 3rd 方库(Guava 版本 20.0)中?

答案是对于这样的 API 应该是什么样子没有达成共识。有太多相互冲突的用例和需求集。

【讨论】:

  • 感谢您的回答。我添加了代码 sn-ps,希望它们有助于澄清问题。我也同意你的说法(The point is that you need to make the choice),但在这种情况下,我无法控制SampleGraph 的实现,所以我不确定。
  • 让我们删除这个对话。它无处可去,
  • 同意。感谢您提供详细的更新答案。
猜你喜欢
  • 1970-01-01
  • 2023-03-24
  • 2022-11-12
  • 2011-05-26
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 2010-11-16
相关资源
最近更新 更多