【发布时间】:2019-05-12 08:13:06
【问题描述】:
问题
考虑一个图的实现,SampleGraph<N>。
考虑一个图节点的实现,Node extends N,正确覆盖 hashCode 和 equals 以反映两个节点之间的逻辑相等。
现在,假设我们要向节点添加一些属性p。这样的属性绑定到节点的逻辑实例,即对于Node n1, n2,n1.equals(n2) 意味着 p(n1) = p(n2)
如果我只是将属性添加为 Node 类的字段,我就遇到过这种情况:
- 我定义
Node n1, n2使得n1.equals(n2)但n1 != n2 - 我将
n1和n2添加到图表中:在插入逻辑节点时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 C 的answer 之后为当前行为和暂定解决方案添加了代码 sn-ps。澄清一下,整个示例来自使用来自开源 Java 项目的真实图形数据结构。
【问题讨论】:
-
S4 对我来说似乎不是一个好的选择。 S3 可能非常复杂,只有在您需要解耦时才值得(例如,如果节点的数据可能完全不同)。 S2 可以,但可能会导致某些地方不会发生(开发人员的错误等)。因此,IMO S1 将是可行的方法,而 S2 可以对其进行补充,以免创建不必要的实例。
-
感谢您的意见。我不确定我是否理解您对 S3 的评论,因为每个逻辑节点的属性应该不同。
-
嗯,S4 不是一个好的选择,部分原因是静态地图。如果 S3 与其他持有地图的类相似,您仍然会遇到同样的问题。因此,建议使用适当的缓存/存储库。此外,每当您加载或删除可能变得非常复杂的节点时,您都必须处理加载和驱逐属性。不正确地这样做可能会导致查找失败(也就是缓存丢失)或如果节点刚刚从图中删除(因为至少缓存/repo 仍会保留引用),则节点不会被垃圾收集。