このStack Overflowの質問は、ポインターを介して親を参照している子に関するものです。
最初は、デザインがひどいアイデアであるというコメントは非常に重要でした。
私はこれがおそらく最善のアイデアではないことを理解しています。一般的な経験則から、「これをしないでください!」
ただし、このようなことを行う必要がある場合、どのような条件が存在するのか疑問に思います。ここでのこの質問と関連する回答/コメントは、グラフでもこのようなことをしないことを示唆しています。
このStack Overflowの質問は、ポインターを介して親を参照している子に関するものです。
最初は、デザインがひどいアイデアであるというコメントは非常に重要でした。
私はこれがおそらく最善のアイデアではないことを理解しています。一般的な経験則から、「これをしないでください!」
ただし、このようなことを行う必要がある場合、どのような条件が存在するのか疑問に思います。ここでのこの質問と関連する回答/コメントは、グラフでもこのようなことをしないことを示唆しています。
回答:
ここで重要なのは、2つのオブジェクトに循環参照があるかどうかではなく、それらの参照が互いの所有権を示しているかどうかです。
2つのオブジェクトが互いに「所有」することはできません。これにより、初期化と削除の順序が扱いにくいジレンマになります。1つはオプションの参照であるか、1つのオブジェクトが他のオブジェクトのライフタイムを管理しないことを示す必要があります。
二重にリンクされたリストを考えてみましょう。2つのノードは相互にリンクしますが、どちらも他方を「所有」しません(リストは両方を所有します)。これは、どちらのノードも他のノードにメモリを割り当てないこと、または他のノードが他のノードのIDまたはライフタイム管理を担当しないことを意味します。
ツリーにも同様の関係がありますが、ツリー内のノードは子を割り当て、親は自分の子を割り当てます。子から親へのリンクはトラバーサルに役立ちますが、やはり所有権を定義しません。
ほとんどのオブジェクト指向設計では、オブジェクトのデータメンバーとしての別のオブジェクトへの参照は所有権を意味します。たとえば、クラスCarとEngineがあるとします。どちらもそれ自体ではあまり役に立ちません。これらのオブジェクトは互いに依存していると言えます。有用な作業を実行するには、他のオブジェクトの存在が必要です。しかし、他のどれが「所有」していますか?この場合、自動車はすべての自動車部品が存在する「コンテナ」であるため、自動車はエンジンを所有していると言えます。オブジェクト指向と現実世界のデザインの両方で、車は部品の合計であり、これらの部品はすべて車のコンテキスト内で互いに接続されています。エンジンはCarへの参照を持っているか、TorqueConverterへの参照を持っています。
循環参照はデザインの悪臭になる可能性がありますが、必ずしもそうとは限りません。慎重に使用し、正しく文書化すると、データ構造の使用が簡単になります。
参照を親と子の間で双方向に移動せずにツリーを横断してみてください。もちろん、脆弱で複雑なスタックベースのアプローチを考え出すこともできますし、ごく単純な参照ベースのアプローチを使用することもできます。
このような設計で考慮すべきいくつかの側面があります。
クラス間の構造依存性:
コンポーネントクラスの再利用を目的とする場合は、不要な依存関係を避け、そのような閉じた循環構造を避ける必要があります。
それでも、2つのクラスが概念的に強く相互リンクしている場合があります。この場合、依存関係を回避することは現実的なオプションではありません。例:ツリーとそのリーフ、またはより一般的にはコンポジットとそのコンポーネント。
オブジェクトの所有権:
1つのオブジェクトが他のオブジェクトを所有していますか?または、別の言い方をすると、1つのオブジェクトが破壊された場合、他のオブジェクトも破壊されますか?
このトピックはSnowmanによって詳細に説明されているため、ここでは説明しません。
オブジェクト間のナビゲーションのニーズ:
最後の問題は、ナビゲーションの必要性です。私のお気に入りの例、ギャングフォーフォーの複合デザインパターンを見てみましょう。
ガンマ&他 「子コンポーネントから親への参照を維持することで、複合構造のトラバースと管理を簡素化できます」もちろん、体系的なトップダウントラバースを想像できますが、非常に大きな複合オブジェクトの場合は、操作が大幅に遅くなり、指数関数的になります。直接参照、さらには円形でも、コンポジットの操作を大幅に容易にすることができます。
例としては、電子システムのグラフィカルモデルがあります。複合構造は、電子ボード、回路、要素を表すことができます。モデルを表示および操作するには、GUIビューでいくつかの幾何学的プロキシが必要です。その後、トップダウン検索を開始するよりも、ユーザーが選択したGUI要素からコンポーネントにナビゲートし、親であり、関連する兄弟/姉妹要素がどれであるかを見つけることは確かにはるかに簡単です。
もちろん、Gamma&alが指摘したように、循環関係の不変量を保証する必要があります。参照するSOの質問が示しているように、これは難しい場合があります。しかし、完全に管理可能で安全な方法です。
結論
ナビゲーションの必要性を過小評価してはなりません。UMLがモデリング表記で明示的にアドレス指定したのは、理由がないわけではありません。そして、はい、循環参照が必要な完全に有効な状況があります。
唯一のポイントは、時々人々はそのような方向にすぐに行く傾向があるということです。そのため、決定を下すかどうかを決める前に、関連する3つの側面すべてを検討する価値があります。
通常、循環参照は循環依存関係を意味するため、非常に悪い考えです。おそらく循環依存が悪い理由はすでにご存知でしょうが、完全を期すために、tl; drバージョンでは、クラスAとBの両方が相互に依存している場合は、AまたはBなしでAまたはBを理解/修正/最適化/などすることは不可能です他のクラスを同時に理解/修正/最適化/など。これはすぐにコードベースにつながり、すべてを変更しないと何も変更できません。
ただし、邪悪な循環依存関係を作成せずに循環参照を使用することは可能です。これは、機能的な意味で参照が厳密にオプションである限り機能します。そのため、クラスから簡単に削除することができ、たとえ動作が遅くなったとしても動作します。このような循環型の非依存性作成参照の主な使用例は、リンクリスト、ツリー、ヒープなどのノードベースのデータ構造をすばやくトラバースできるようにすることです。たとえば、原則として、二重リンクリストで実行できる操作は、単リンクリストでも実行できます。たまたまいくつかの操作(リスト内を逆方向に移動するなど)がありますが、 O二重リンクバージョン。
通常これを行うのは良い考えではない理由は、それがDependency Inversion Principleに違反するためです。人々はこの投稿で私が十分にカバーすることができるよりもはるかに詳細にこれについて多くのことを書いたが、カップリングが非常にきついので、維持するのを難しくすることに要約される。どちらかのクラスを変更すると、ほとんどの場合、もう一方のクラスの変更が必要になりますが、依存関係が一方向のみを指す場合、インターフェイスの片側の変更は分離されます。両方のクラスが抽象インターフェースを指す場合、さらに良いです。
1つの主な例外は、異なる抽象化レベルで2つの異なるクラスを持たず、同じクラスの2つのノード(ツリー、二重リンクリストなど)がある場合です。ここでは、構造的関係よりも抽象関係。アルゴリズムの効率化のための循環参照は受け入れられ、このような場合には奨励されます。
[...]グラフでさえ、このようなことをしないことを提案します。
ツリーがトップダウン方式で物事にアクセスする必要がある一方で、ツリーとは異なるデータ構造からボトムアップ方式で物事にアクセスする必要がある場合があります。
例として、四分木はベクトルグラフィックソフトウェアに要素を格納する場合があります。ただし、ユーザーの選択は、ベクトル要素の参照/ポインターの個別の選択リストに保存されます。ユーザーがその選択を削除したい場合は、クアッドツリーを更新する必要があります。トップダウンではなく、リーフから開始してボトムアップでツリーを更新する方がはるかに効率的です。それ以外の場合は、各要素について、ルートからリーフまで作業してから、再度バックアップする必要があります。
Doom 3には、親オブジェクトへのポインターを持つ子オブジェクトの例があります。特に侵入型リストを使用します。要約すると、侵入型リストはリンクされたリストに似ていますが、各ノードにはリスト自体へのポインターが含まれている点が異なります。
利点:
オブジェクトが複数のリストに同時に存在できる場合、リストノードのメモリを割り当てる必要があるのは一度だけです。
オブジェクトを破棄する必要がある場合、各リストを直線的に検索することなく、オブジェクトが含まれているすべてのリストから簡単に削除できます。
これは非常に具体的なシナリオだと思いますが、あなたの質問を理解すると、親オブジェクトへのポインターを含む子オブジェクトの許容可能な使用例です。