我想在 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 类来维护图结构的完整性。这是这样做的正确方法还是设计从一开始就被破坏了?

有帮助吗?

解决方案

我经常使用 Java 中的图形结构,我的建议是将图形依赖于维持其结构的 Node 和 Edge 类的任何数据成员设为 Final,而不使用 setter。事实上,如果可以的话,我会让 Node 和 Edge 完全不可变,这已经 很多好处.

因此,例如:

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);
    }
}

如果需要修改节点名称,请删除旧节点并添加新节点。这听起来像是额外的工作,但它可以节省大量精力,让一切顺利进行。

不过,实际上,从头开始创建自己的图结构可能是不必要的——如果您构建自己的图结构,这个问题只是您可能遇到的许多问题中的第一个。

我建议找到一个好的开源 Java 图形库,并使用它。根据您正在做什么,有几种选择。我用过 荣格 过去,并会推荐它作为一个很好的起点。

其他提示

我不清楚为什么要为节点添加字符串名称的额外间接寻址。你的 Edge 构造函数的签名像这样不是更有意义吗 public Edge(String, Node, Node) 代替 public Edge (String, String, String)?

我不知道克隆能在哪里帮助你。

预计到达时间:如果危险来自于创建节点后更改节点名称,则抛出 IllegalOperationException 如果客户端尝试在具有现有名称的节点上调用 setName()。

在我看来,除非您明确声明您的数据结构可以克隆该元素,否则您永远不应该克隆该元素。

大多数事物的所需功能需要通过引用将实际对象传递到数据结构中。

如果你想使 Node 类更安全,使其成为图的内部类。

对我来说,使用 NodeEnvelopes 或边缘/节点工厂听起来像是过度设计。

您真的想在 Node 上公开 setName() 方法吗?您的示例中没有任何内容表明您需要它。如果您将 Node 类和 Edge 类设置为不可变,那么您设想的大多数完整性违规场景都将变得不可能。(如果您需要它们是可变的,但仅在它们添加到图形之前,您可以通过在 Node/Edge 类上设置 isInGraph 标志来强制执行此操作,该标志通过 Graph.Add{Node, Edge} 设置为 true,并且如果在设置此标志后调用,则让您的变异器抛出异常。)

我同意 jhkiley 的观点,将 Node 对象传递给 Edge 构造函数(而不是字符串)听起来是个好主意。

如果您想要一种更具侵入性的方法,您可以有一个从 Node 类返回到它所在的 Graph 的指针,并在 Node 的任何关键属性(例如名称)发生变化时更新 Graph。但我不会这样做,除非您确定需要能够更改现有节点的名称,同时保留边缘关系,这似乎不太可能。

Object.clone() 有一些重大问题,在大多数情况下不鼓励使用它。请参阅第 11 条,来自“有效的Java”约书亚·布洛赫(Joshua Bloch)的完整答案。我相信您可以在原始类型数组上安全地使用 Object.clone() ,但除此之外,您需要明智地正确使用和覆盖克隆。您最好定义一个复制构造函数或一个静态工厂方法,根据您的语义显式克隆对象。

除了 @jhkiley.blogspot.com 的评论之外,您还可以为边和节点创建一个工厂,拒绝创建名称已被使用的对象。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top