web-dev-qa-db-ja.com

2つのノードタイプを持つツリーに最適なオブジェクト指向設計アプローチは何ですか?

下の図に示すように、ツリーのような構造です(1つの小さな例として)。ツリーは、次の2つの異なるノードタイプで構成されています。

  1. データノード:黄色で色分けされているこれらのノードには、約10個の属性が含まれています。彼らは常に木の葉に現れます。
  2. コレクションノード:青で表示されます。彼らはIdおよびType属性をData Nodesと共有しており、子の順序付きリストも保持しています。

このツリーは1つのコンポーネントで作成され、操作(保存、GUIでの表示など)のために別のシステムに送信されます。このツリーを設計するには、以下で説明するさまざまなアプローチがあり、その長所と短所があります。それらについてどう思いますか、この場合、どれが最も適切ですか?

アプローチ1:共通の機能(例:getIdgetType)のインターフェースを用意し、特定の機能(例: getChildren)具象クラスで。このように、ツリーを操作する必要のある他のアプリケーション(GUIで保存または表示するため)は、ノードのタイプに基づいて多くのダウンケースを必要とします。

アプローチ2:両方のノードタイプのすべての機能に対して1つのインターフェイスを用意します。各具象クラスで、メソッドを実装するか(該当する場合)、例外をスローします。ノードのタイプに基づいて適切なメソッドを呼び出すのは、ユーザーの責任です。

アプローチ3:インターフェイスにアクセスしないでください。ただし、両方のノードタイプのすべてのデータ+メソッドを含む1つのクラス(例:TreeNode)のみがあります。ここでも、適切なオブジェクトタイプで適切なメソッドを呼び出すのはユーザーの責任です。

p.s。必要に応じて、実装言語はC++ 11になります。


更新1:このツリーの詳細と、より具体的な要件を以下に示します。

  1. ツリーは、提供されたAPIを使用してソースシステムで作成されます。このコンテキストでは、ユーザーはツリー階層を作成し、各ノードの値を設定します。
  2. このツリーを使用する2番目のシステムは、ツリーの保存/ロードと、GUIでの表示を担当します。これらのアクションは通常、ツリーのすべてのノード上を歩く必要があります。さらに、この2番目のシステムは、GUIを使用してツリー要素を編集したり、ツリーでいくつかの値を検索したりできます。

A simple example of the described tree

1
Gupta

あなたが言及している3つ以外の別のオプションは、データを独自のクラスに分離し、オプションの方法でそれと子の両方を提供する基本的なノードインターフェイスを持つことです。

子にとって、これは簡単です。子のないノードには、子がまったくありません。

データの場合、戻り値の型として_std::optional<NodeData&>_を使用するか、2つのメソッド(hasNodeData、getNodeData)を使用できます。

これはまだそこにデータがあるかどうかを確認するために他のコードを必要とします(それを回避する方法はありません、AFAIK)。

それは完璧ではありません-最上位のインターフェースにNodeDataを含めているので、さまざまなノードタイプがあり、すべてが異なるデータを持っている場合、これは扱いにくくなる可能性があります。しかし、あなたが説明する場合のために、これはかなりクリーンなアプローチIMOでしょう。

任意の数のタイプに対応できるアプローチは、そのタイプのデータに対してstd :: optionalを返すテンプレート化されたgetDataメソッドです。

例:if(auto fooData = mysteryNode.getData<FooData>()) ...

この状況ではやり過ぎかもしれませんが、最も一般的なインターフェースになります。

4
Errorsatz

アプローチ1と2はどちらも有効です。最も適切なものを選択するための重要な問題は、これらのオブジェクトをクライアントコンテキスト(GUI、永続性など)でどのように均一に処理するかです。 複合デザインパターン は、次のことを目的としているため、役立つ場合があります。

オブジェクトをツリー構造に構成して、部分全体の階層を表します。 Compositeを使用すると、クライアントは個々のオブジェクトとオブジェクトの構成を均一に処理できます。

主な問題は、最小限の一般的な動作に統一された処理が必要か(アプローチ1)、またはより野心的であると予想されるか(アプローチ2)です。 GoF この問題について2つの側面(167ページ)で説明します。

  • コンポーネントインターフェースの最大化」:インターフェースが一般的であるほど、クライアントコードはより多様になります。ただし、udnerに完全に無関係なものを同じ傘に入れると、 インターフェース分離の原則 と競合するリスクがあります。
  • "Declaring child management operations"より正確には、どのレベルで:
    • コンポジットレベルでは、コンパイル時に問題を検出できますが、よりリスクの高いダウンキャストを意味する場合があります。また、クライアントコードは、すべての具象クラスの微妙な違いに対処する必要もあります。これは 最小知識の原則 と一致しません。
    • コンポーネントレベルでは、クライアントコードの均一性が大幅に向上しますが、ユーザーインターフェイスで異なるノードを異なる方法で処理する場合は、例外を防止(または処理)するために、クライアント側でさらにチェックを行う必要があります(この場合も、最小の知識の原則)。

したがって、完璧な解決策はありません。それは、あなたの意図を考慮して見つけるのは微妙なバランスです。あなただけがそれを見つけることができます。

実は確かです。アプローチ3は避けなければなりません。これは、懸念の健全な分離と オープン/クローズの原則 に反します。この設計は、時間の経過とともに維持するのが困難になるモノリシックコードにつながります。たとえば、ある日、さまざまな種類のコレクションまたはさまざまな種類のデータノードが必要であるという結論に達したとします。アプローチ1や2では大きな問題にはなりません。しかし、アプローチ3では、すべてを確認する必要があります。

2
Christophe

ノードクラスの1つのタイプのみが表示されます:ノード。ノードのリストであるプロパティChildNodesがあります。これは空でもかまいません。 Nodeにはnullの可能性があるプロパティDataもあります。Dataはデータプロパティを含む別のクラスです。

コレクションノードとは、子を持ち、データを持たないノードです(Data == null)。データノードには、0個の子と1つのDataオブジェクトがあります。あなたは両方を持つことができます(なぜないのですか?)。それが気に入らない場合は、子がデータノードに追加されるか、データがコレクションノードに追加されるとすぐにスローします。

2
Martin Maat