新しい要素を追加するときはクローンを使用する必要がありますか?クローンはいつ使用する必要がありますか?
質問
グラフのデータ構造を扱うクラスをJavaで実装したいと考えています。NodeクラスとEdgeクラスがあります。Graph クラスは 2 つのリストを維持します。ノードのリストとエッジのリスト。各ノードには一意の名前が必要です。このような状況を防ぐにはどうすればよいですか:
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 にすることをお勧めします。実際、可能であれば、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);
}
}
ノードの名前を変更する必要がある場合は、古いノードを削除して、新しいノードを追加します。余分な作業のように聞こえるかもしれませんが、すべてを正しく保つために多くの労力を節約できます。
ただし、実際には、独自のグラフ構造を最初から作成する必要はおそらくありません。この問題は、独自のグラフ構造を構築する場合に遭遇する可能性のある多くの問題のうちの最初の 1 つにすぎません。
優れたオープンソース 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 に設定することでこれを強制できます。このフラグが設定された後に呼び出された場合、ミューテーターが例外をスローするようにします。)
Node オブジェクトを (String の代わりに) Edge コンストラクターに渡すのは良いアイデアのように思えるという jhkiley の意見に私も同意します。
より侵入的なアプローチが必要な場合は、Node クラスからそれが存在するグラフへのポインタを保持し、ノードの重要なプロパティ (名前など) が変更された場合にグラフを更新することができます。ただし、Edge の関係を維持しながら既存のノードの名前を変更できる必要があることが確実でない限り、そのようなことはしません。これは可能性が低いと思われます。
Object.clone() にはいくつかの大きな問題があるため、ほとんどの場合、その使用は推奨されません。項目 11 を参照してください。効果的なJava完全な回答については、Joshua Bloch による。プリミティブ型配列で Object.clone() を安全に使用できると思いますが、それとは別に、clone の適切な使用とオーバーライドについては慎重に行う必要があります。おそらく、セマンティクスに従って明示的にオブジェクトのクローンを作成するコピー コンストラクターまたは静的ファクトリ メソッドを定義する方がよいでしょう。
@jhkiley.blogspot.com によるコメントに加えて、既に使用されている名前のオブジェクトの作成を拒否するエッジとノードのファクトリを作成できます。