添加新元素时应该使用克隆吗?什么时候应该使用克隆?

时间:2020-03-05 18:53:40  来源:igfitidea点击:

我想用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类,该类将保持图的结构完整性。这是正确的做法,还是从一开始就破坏了设计?

解决方案

回答

我认为,除非明确声明数据结构已做到这一点,否则切勿克隆该元素。

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

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

回答

我不清楚,为什么要为节点添加字符串名称的其他间接寻址。将Edge构造函数的签名改为" public Edge(String,Node,Node)"而不是" public Edge(String,String,String)"会更有意义吗?

我不知道克隆在哪里可以为我们提供帮助。

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

回答

除了@ jhkiley.blogspot.com的评论外,我们还可以为Edges和Nodes创建一个工厂,该工厂拒绝创建具有已使用名称的对象。

回答

我经常使用Java处理图结构,我的建议是使Graph所依赖的Node和Edge类的任何数据成员保持其结构最终不变,而无需使用设置程序。实际上,如果可以的话,我将使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);
    }
}

如果需要修改节点的名称,请删除旧节点并添加一个新节点。这听起来像是额外的工作,但可以节省很多精力,确保一切正常。

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

我建议我们找到一个好的开源Java图形库,然后改用它。根据工作,有几种选择。我过去曾经使用过JUNG,因此建议将其作为一个很好的起点。

回答

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

我们是否真的想在Node上公开setName()方法?示例中没有任何内容表明我们需要这样做。如果将Node和Edge类都设置为不可变的,那么我们构想的大多数违反完整性的情况都将变得不可能。 (如果我们需要使它们可变但仅在将它们添加到Graph之前,则可以通过在Node / Edge类上具有isInGraph标志(由Graph.Add {Node,Edge}设置为true)来强制实施此操作。如果在设置此标志后调用,则使更改程序抛出异常。)

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

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

回答

Object.clone()有一些主要问题,在大多数情况下,不建议使用它。请参阅Joshua Bloch撰写的" Effective Java"中的第11项,以获取完整的答案。我相信我们可以在原始类型数组上安全地使用Object.clone(),但除此之外,我们还需要谨慎地正确使用和覆盖克隆。最好定义一个复制构造函数或者一个静态工厂方法来根据语义显式克隆对象。