私は他の人と一緒にプロジェクトに取り組んでおり、情報の非表示と静的な型の安全性について話し合っています。以下にシナリオを示します。
言語:C++ 11
シナリオ:ツリーのような構造を作成します。各ツリーノードには独自のクラスがあり、すべて基本クラスNodeType
のサブタイプです。各サブタイプには、他のノードをリンクする独自のルールがあります。たとえば、NodeTypeA
、NodeTypeB
、NodeTypeC
はNodeType
のサブクラスであり、
NodeTypeA
は、最初の子としてNodeTypeB
、2番目の子としてのみNodeTypeA
を持つことができます。NodeTypeB
は、その子孫としてNodeTypeB
のみを持つことができます。 (それは任意の数の子を持つことができます)NodeTypeC
は、最初の子としてNodeTypeA
、2番目の子としてのみNodeTypeB
を持つことができます。例には再帰の問題があるかもしれませんが、説明のためには問題ありません。
これで、各ノードを作成するFactory
クラスがあります。
createNodeTypeA
createNodeTypeB
createNodeTypeC
ユーザーテキスト入力をツリーに変換したいBuilder
クラスがあります。
Builder
は各ノードのタイプに関する情報を知らない必要はありません。 Factory
から取得したポインタを保持し、それらを別のFactory
のメソッドに渡します。 Builder
の観点では、すべてのノードのタイプを基本クラスNodeType
にすることができます。
ジレンマ:
さらにInformation Hidingを重視する場合、ファクトリのメソッドは次のようになります。
NodeType *createNodeTypeA(NodeType *first, NodeType *second)
NodeType *createNodeTypeB(std::vector<NodeType *> children)
NodeType *createNodeTypeC(NodeType *first, NodeType *second)
正確さを提供するために、ファクトリーメソッドの各パラメーターの実行時チェックを提供する必要があります。
Static Type Safetyをさらに重視する場合、ファクトリのメソッドは次のようになります。
NodeTypeA *createNodeTypeA(NodeTypeB *first, NodeTypeA *second)
NodeTypeB *createNodeTypeB(std::vector<NodeTypeB *> childron)
NodeTypeC *createNodeTypeC(NodeTypeA *first, NodeTypeB *second)
およびBuilder
は、各ノードのサブタイプを知っている必要があります。私たちが得るものは型安全です。
次の図は、2つの方法を示しています。
ディスカッション:
好意情報非表示:
Builder
の開発者は、ノードのサブタイプの知識がなくても作業できます。優先静的型安全性:
Factory
のメソッドを呼び出すと、型情報(関数名で示される)がBuilder
に公開されます。 Builder
に期待されるもう1つのことは、それを保存することです。質問:
どれが良い習慣かもしれませんか?私たちは、長期的な影響をより重視します。
NodeType *createNodeTypeA(NodeType *first, NodeType *second)
NodeType *createNodeTypeB(std::vector<NodeType *> children)
NodeType *createNodeTypeC(NodeType *first, NodeType *second)
これは「情報を非表示にする」ことはなく、Builderはどのファクトリを呼び出すか、どのファクトリに渡す必要があるかを認識しています。したがって、動的タイプを使用しても、この場合は何のメリットもないようです
Builderを実際にノードタイプのないものにするために何ができるか、単一のファクトリを使用できます。
NodeType *createNode(std::vector<NodeType*> children)
ただし、これではノードタイプを指定できません。これはしばしば構築の場合に当てはまり、一般に疑問を投げかけます。原則として、ビルダーからノードタイプ情報を削除することは可能ですか?おそらく、デカップリングは、同調/先取りの側にあるべきです。
情報非表示を優先:
- 情報自体を隠すことは重要です。 Builderの開発者は、ノードのサブタイプの知識がなくても作業できます。
ファクトリメソッドに渡される引数は特定の(ランタイム)タイプである必要があるため、これは正しくありません。したがって、プログラムは、コンパイル時にチェックされるか実行時にチェックされるかに関係なく、ツリーを構築するときにサブタイプを認識する必要があります。
- コードレビュー、テスト、自動分析ツールにより、静的型安全性の損失を補うことができます。資格のあるチームは、それらをかなりうまく完了することが期待されています。
または、コンパイラーに処理を任せることで、多くの作業を節約できます。
本当の問題は、あなたのインターフェースがうまく設計されていないことです。異なるサブタイプは相互の知識を必要とするため、それらは独立しておらず、互換性もありません。設計が実際に提供する拡張性と実際に必要な拡張性を自問してください(近い将来現実的に発生する可能性のあるユースケースが思いつかなければ、それは必要ありません)。
この設計の失敗の原因は、ツリーデータ構造を煩わしく表現していること、つまりノード間の関係をノード自体のデータとして保存していることにあると思います。あなたが言及するサブタイプの唯一の違いは、他のノードとの関係が異なることです。したがって、これらの関係をノードの表現から分離すると、ノードをサブタイプ化する必要がなくなる、または少なくとも、より優れた抽象化を提供できる場合があります。
このような変更の難しさは、ノード間の関係を使用する機能をグラフ(検索)アルゴリズムとして再構成する必要があることです。多くの場合、これには考え方の変更が必要です。 ブーストグラフライブラリ を使用すると、これに役立ち、ツリー構造、ノード/エッジデータ表現、およびアルゴリズムを簡単に分離できるようにデータ構造とアルゴリズムが提供されます。
それは不完全な比較です
「情報の隠蔽」と「静的データ型付け」は対立する概念ではなく、相互に排他的でも絶対的でも相対的でもありません。
これは @ DDmmr回答が触れる-設計 である本当の問題から遠ざかる誤った二分法です。次に、関連するコメントが重要な手がかりを示します。 ツリー構造は、単純なスクリプトの抽象構文ツリー(AST)です
デザインに「簡単なスクリプト」の文法がありません
ユーザーのテキスト入力をツリーに変換したいビルダーBuilderがあります。
この設計ステートメントは、構文解析をOPの質問に直接つながるオブジェクトの構築と統合することをお勧めします。解析には、「単純なスクリプト」の定義された構造が必要です。この定義は文法と呼ばれます。
文法は、「単純なスクリプト」の基本的なビット、許容される組み合わせ、および組み合わせの組み合わせなどを記述します。必然的に特定の(複雑な)ビットは、Node
sおよびNodeサブタイプ(および関連する可能性のあるプロパティ))として直接マッピングされます。
重要なのは、基本的なものから最も複雑なものまで、これらすべての異なるビットに名前があります。 「番号」、「キーワード」、「数字」、「文字」、「演算子」など.
懸念の分離
AST "名前付きノード"はメタデータであり、まだ構築されていない具体的なNode
オブジェクトの抽象化です。このメタデータは、AST Node
ツリーの実装。
その場合、デザインは3つの一般的な部分になります。定義された文法、構文解析、具体的なNode
オブジェクト構築です。
ノード実装設計
解析の出力はASTです。しかし、最終的なNode
ツリーはASTから派生したものであり、ASTではありません。しかし、私は確かに混乱を見ることができます。これは人工的な抽象概念のように感じますが、重要な設計ポイントです。
今ビルダーはASTを取ります。 Node
リレーションルールは既にASTの構造に組み込まれていることに注意してください。したがって、ビルダーは、AST構造のみに基づいてNode
ツリーを構築しますが、これらのルールの明確な知識は必要ありません。
「単純なスクリプト」は本格的な汎用スクリプト言語ではないので、解析は本当に簡単ですが、これが単一責任a.k.a.懸念の分離に違反することを許してはいけないと思います。
おそらくASTをスクリプトの基本粒子の要素/ノードに完全に解析する必要はありません。多分NodeMetaData
オブジェクトがあるとしましょうAST Node
スーパークラスのプロパティに対応します。
OP quandaryは設計されています
「情報の非表示」は、懸念事項の分離と慎重に設計されたBuilder
を考えると、本質的に適切です。静的型パラメーターは、明確に定義されたメタデータを持つASTオブジェクトに道を譲ります。
Builder
とファクトリーは、Node
もサブタイプオブジェクトも引数として取りません。 Builder
はAST=をステップスルーし、ファクトリーはASTメタデータに基づいてインスタンス化および呼び出されます。ビルダーはNodeTree
を工場で作成されたNodeTree
パーツから組み立てます。
ノードツリーの構築は潜在的に複雑なので、「ビルダー」や「ファクトリー」などの構築設計パターンを調べます。ファクトリパターンは複雑さによって異なることに注意してください。
私はもう少しコンテキストが高く評価されていたと思います:)。
あなたのジレンマを言い換えさせてください:
ツリーを構築するアルゴリズムは、さまざまなノードタイプを認識し、有効なツリーを構築する必要があります。アルゴリズムがノードのストリームを提供されている場合、チェックした後、どのノードがどこに行くべきかをアルゴリズムが通知できるはずです。無効な入力を検出したり、無効なツリーを作成するよう強制されたりした場合は、単に停止してエラーを出力します。
したがって、Tree
、NodeType
、findChildren
、getRoot
などのgetSize
をパラメーターとして取るgetDepth
を含むonlyメソッドを定義します。メソッドParser
を返すbuildTree
(algorithm)を呼び出すと、 NodeType
(これはツリーと同等です!!!)。 NodeTypeA
、NodeTypeB
などの列挙型を含むことができるNodeTypeC
のペイロードを調べることにより、ノードがNodeType
、TypeA
、およびTypeB
であるかどうかを判断します。ノードタイプを区別するすべてのロジックはParser
にあります。
結論として、さまざまなノードタイプを認識するParser
(またはアルゴリズム)と呼ばれるものを使用して、非常に一般的なツリーの処理を実行できるTree
抽象化から懸念を分離しました。
情報の非表示は外部的なメリットです。クラスAが与えられた場合、Aは情報を非表示にするメリットがありません。それはクラスがその仕事をするのを助けません、そしてそれはコードについての開発者の理由を助けません。役に立たない知識にさらされる必要のない外部の開発者を支援します。情報を非表示にすることは、クラスが他のクラスに対して行う好意です。
静的型チェックは、クラスとクラス開発者にとってメリットがあります。検証を少なくする必要があるため、クラスにメリットがあります。クラスAとメソッドFooを指定します。これは整数を受け取り、10進数を返します。メソッドFooは、整数nullか配列か何かを確認する必要はありません。その確認は、コンパイル時または実行時にそのメソッド内の他の場所で行われ、整数になります。 Fooがプライベートの場合、10進数を返します。これは開発者にとってのメリットです。
さて、あなたが本当にこのトレードオフをする必要があるかどうかは言えませんが、情報の隠蔽は省略できます。ネガティブは外部的なものであり、直接的な問題ではありません。
コメントに応じて更新する:並べ替えアルゴリズムの手続き型実装があり、いくつかのグローバル変数といくつかの関数を使用して中間結果を返すとします。非常に堅実で、高速で、効率的で、大きなOです。オブジェクト指向設計を使用して書き換えるのに最適なターゲットではありませんが、残りのアプリケーションを実行します。これをクラスでラップするだけでも、高速で効率的でバグのない堅固な実装です。グローバル(現在はクラスレベルのフィールドまたはプロパティ)とプライベートメソッドを非表示にしても、高速化したり、クラス内のバグを排除したりすることはできません。これは、安全に使用するために内部でどのように動作するかの詳細を他の誰かが学ぶ必要がないようにします。架空の並べ替えクラスのメリットはゼロであり、アプリケーションにとっては大きな勝利です。