我想在Java中实现一个用于处理图形数据结构的类。我有一个Node类和一个Edge类。 Graph类维护两个列表:节点列表和边缘列表。每个节点必须具有唯一的名称。我如何防范这样的情况:
Graph g = new Graph();
Node n1 = new Node("#1");
Node n2 = new Node("#2");
Edge e1 = new Edge("e#1", "#1", "#2");
// Each node is added like a reference
g.addNode(n1);
g.addNode(n2);
g.addEdge(e1);
// This will break the internal integrity of the graph
n1.setName("#3");
g.getNode("#2").setName("#4");
我相信我应该在将它们添加到图形时克隆节点和边缘,并返回一个将保持图形结构完整性的NodeEnvelope类。这是正确的做法还是设计从一开始就被打破了?
答案 0 :(得分:4)
我在Java中使用图形结构很多,我的建议是创建Graph所依赖的Node和Edge类的任何数据成员,以保持其结构最终,没有setter。事实上,如果可以,我会使Node和Edge完全不可变,其中many benefits。
所以,例如:
public final class Node {
private final String name;
public Node(String name) {
this.name = name;
}
public String getName() { return name; }
// note: no setter for name
}
然后,您将在Graph对象中执行唯一性检查:
public class Graph {
Set<Node> nodes = new HashSet<Node>();
public void addNode(Node n) {
// note: this assumes you've properly overridden
// equals and hashCode in Node to make Nodes with the
// same name .equal() and hash to the same value.
if(nodes.contains(n)) {
throw new IllegalArgumentException("Already in graph: " + node);
}
nodes.add(n);
}
}
如果需要修改节点名称,请删除旧节点并添加新节点。这可能听起来像是额外的工作,但它可以节省很多努力,保持一切顺利。
但是,真的,从头开始创建自己的Graph结构可能是不必要的 - 这个问题只是你构建自己的问题时可能遇到的许多问题中的第一个。
我建议找一个好的开源Java图形库,然后使用它。根据您的工作情况,有一些选项可供选择。我过去曾使用JUNG,并建议将其作为一个很好的起点。
答案 1 :(得分:3)
我不清楚为什么要为节点添加字符串名称的附加间接。你的Edge构造函数的签名是public Edge(String, Node, Node)
而不是public Edge (String, String, String)
更不合理吗?
我不知道克隆会在哪里帮助你。
ETA:如果危险来自于在创建节点后更改节点名称,如果客户端尝试在具有现有名称的节点上调用setName(),则抛出IllegalOperationException
。
答案 2 :(得分:1)
在我看来,除非明确说明你的数据结构是这样做的,否则你不应该克隆元素。
大多数事物的所需功能需要通过引用将实际对象传递到数据结构中。
如果您想让Node
类更安全,请将其设为图的内部类。
答案 3 :(得分:1)
使用NodeEnvelopes或边缘/节点工厂听起来像是过度设计给我。
你真的想在Node上暴露一个setName()方法吗?您的示例中没有任何内容表明您需要它。如果您使Node和Edge类都不可变,那么您想象的大多数完整性违规方案都变得不可能。 (如果您需要它们是可变的,但只有在它们被添加到Graph中之后,您可以通过在Graph.Add {Node,Edge}上设置为true的Node / Edge类上具有isInGraph标志来强制执行此操作。如果在设置此标志后调用,则让mutators抛出异常。)
我同意jhkiley将Node对象传递给Edge构造函数(而不是Strings)听起来是个好主意。
如果您想要一种更具侵入性的方法,您可以将Node类的指针返回到它所驻留的Graph,如果Node的任何关键属性(例如名称)发生变化,则更新Graph。但我不会这样做,除非你确定你需要能够在保留Edge关系的同时更改现有节点的名称,这似乎不太可能。
答案 4 :(得分:1)
Object.clone()存在一些主要问题,在大多数情况下不鼓励使用它。请参阅Joshua Bloch的“Effective Java”中的第11项,以获得完整的答案。我相信你可以安全地在原始类型数组上使用Object.clone(),但除此之外,你需要明智地正确使用和重写克隆。您最好定义一个复制构造函数或静态工厂方法,根据您的语义显式克隆该对象。
答案 5 :(得分:0)
除了@ jhkiley.blogspot.com的评论之外,您还可以为边缘和节点创建一个工厂,拒绝创建具有已使用名称的对象。