web-dev-qa-db-ja.com

これは単一責任原則違反ですか?

OOグラフライブラリを設計しており、現在GraphEdgeクラスの設計を理解しようとしています。そのノードのセッターとゲッターを追加しました、方向、重量。これは完全に妥当なようです。

次に、Graphクラスの作業を開始し、特定のEdgeが特定のノードに隣接しているかどうかを知る必要が出てきました。

そして今、私はGraphEdgeクラスにisAdjacentTo(Node)メソッドを追加するべきか、それともGraphUtil class(*)を作成して静的なedgeIsAdjacentToNode(Edge,Node)メソッドを追加するべきか疑問に思っています。それに。

一方では、最初のオプションに固執する場合、GraphEdgeを変更する追加の理由がわかりません。一方、これはクラスに追加された一種の第二の責任のように思えます。

また、最初のオプションに固執する場合、これはおそらくOpen-Close Principleに違反します。 GraphEdgeは、変更のために閉じられていません。後でlinksNodes(Node,Node)メソッドを追加する必要があるためです。

質問は次のとおりです。

  • isAdjacentTo(Node)メソッドをGraphEdgeクラスに追加すると、SRPに違反しますか?

  • どうして?

(*)私は純粋なOOスタイルに固執し、無料の機能はありません。

5
Kolyunya

あなたは、最も単純で最も楽しい方法で物事を成し遂げることに集中するのではなく、オーバーエンジニアリングの分野に足を踏み入れ、抽象的な原理を追い求めてきたと思います。

あなたの(*)は明確なプレゼントです。 OOは、複雑さを管理する上で非常に役立ちますが、それがその目標へのツールとして使用されている場合に限られます。代わりに、「問題を解決する」のではなく「純粋なOOを書く」場合は、負担になります。

SRPも同様です-考える価値は十分あります。また、現在のコードが非常に多くのタスクで過負荷になっている場合に、「抽出」または「分割」タイプのリファクタリングを実行するのに特に有効です。 DRY=に対処した後でも、通常、パッケージ化には多くのオプションがありますが、それはバランスの問題です。クラスが大きすぎることは悪いことですが、クラスが1つであることには利点があります。同じタスクを実行した場合5クラスの場合、追跡が難しくなる可能性があります。特に、一人で作業する機会がない場合は、全方向のクロスコールと結合されます。このような分割は、無意味な統計では見た目が良いかもしれませんが、とにかく練習には適していません。

理論についてはこれくらいですが、あなたの場合はどうですか?あなたは国境問題を選びました。確かに、この関数はNodeまたはEdgeのどちらにも当てはまらない場合があります。コンテンツなしではわかりにくいですが、実際に何をしているのかはわかりません。NodeまたはEdge?可能であれば、隣接は同じ関数グループに適合します。

実際に関数を実装したらすぐに、関数がより適切に適合する場所、つまりどのデータをフェッチする必要があるかを確認することは非常に役立ちます。たとえば、ノードコレクションとエッジコレクションをグラフから取得することから始める場合、いったいなぜそれが単なるグラフのメンバーではなく、ホームグラウンドでプレイしないのでしょうか。

無料の機能も公平なゲームかもしれません。本当にゴミ箱にすぎない* Utils偽クラスはおそらく最悪の方法です。しかし、言語または誤った考えの方針がそれを強制するかもしれません。

9
Balog Pal

答えは「どちらでもない」です。提案されたソリューション両方の問題は、どちらも Information Expert が知る必要があること(単一のノードが他のすべてのノードにどのように関連するか)でないことです)。

この情報を計算できる無料の関数を作成する唯一の方法は、すべてのノードを引数として関数に渡すことです。それ以外の場合は、フリー関数はないが、 脆いグローバル状態 です。指先にすべてのノードがすでにある場合、本当にその機能が必要ですか?

さらに重要なことに、すべてのノードが指先にあるオブジェクトにこのメソッドがないのはなぜですか? <-ヒント、ヒント

今度は、おそらく二重リンクリストのようなものを実装しているが、前後の方向だけでなく、この情報にアクセスできるノードを持つことができます。この場合、ノードが特定のノードがそのnextNode、previousNode、upNode、またはdownNodeにあるかどうかを通知することは簡単です。しかし、この方法でノードをコーディングしなかったのではないかと思います。そうしないと、質問をしません。だからヒントを使ってください;).

1
Amy Blankenship

TL; DR

このフレーズは、SRPを時期尚早に適用しないことを思い出させるのに役立ちます。

アプリケーションの動作方法の全体像が十分に理解されている場合にのみ、責任の適切な分離が行われます。

出典: http://www.oodesign.com/single-responsibility-principle.html


API要件フェーズ

これらは例として提供されています。アプリケーションに不要なものを取り消します。
残りの回答では、すべてが必要であると想定しています。要件が異なる場合は、独自の要件に合わせるために原則を再適用する必要があります。

  • ライブラリユーザーとして、Edgeのインスタンスがある場合、Edgeで接続されている2つのNodesをクエリできるはずです。これにはO(1)時間かかるはずです。
  • ライブラリー・ユーザーとして、Nodeのインスタンスがある場合、ノードに接続するEdgesのリストを照会できるはずです。これには、その1つのノードに接続されているエッジの数であるO(num_edges_of_node)の時間がかかります。
  • ライブラリユーザーとして、NodeのインスタンスとEdgeのインスタンスがある場合、EdgeNodeが直接接触しているかどうかを確認できるはずです。これにはO(1)時間かかるはずです。
  • 必要に応じて要件を追加します。

思考の学校:Naive-OOとLibrary-Design-OOの間

Naive OO designでは、メソッドがユーザーがその機能を使用したい場所にあるオブジェクト。

  • Node[] theTwoNodes = someEdge.getNodes()
    (返される配列サイズは2でなければなりません)
  • Edge[] allConnectedEdges = someNode.getEdges()
    (返される配列サイズは0以上にすることができます;無制限です。)
  • bool isThisConnectedToThat = thisNode.isConnectedTo(thatEdge)
    bool isThatConnectedToThis = thatEdge.isConnectedTo(thisNode)
    1つ目、2つ目、または両方を実装します。

Naive OO設計でメソッドを実装する場合、Information Expert- Amy Blankenship's answer を参照してください。

  • つまり、API要件は、ユーザーがこれを実行したいことを示していますが、必要な機能を実装するには、インスタンスが別のインスタンスに情報を要求する必要があります。
  • 「情報エキスパート」は、メソッドを他のインスタンス(その機能を提供するための準備ができているほとんどの情報を持つインスタンス)に関連付ける場合、少ないコードで記述できる経験則です。
  • サンプルコードでは、情報エキスパートの場所は、選択したデータ構造とアルゴリズムによって異なります。
    通常、それはGraphオブジェクトに集中するか、多数のNodeおよびEdgeインスタンスに分散されます。
    それでも、情報エキスパートが別の場所に配置されている場合でも、私の答えは無効になりません。

ただし、Pythonic-OOでは、ライブラリ作成者は2つのisConnectedToメソッドのいずれかを削除する必要があります。このため:

それを行うには、1つ---できれば1つ--明白な方法が必要です。

ソース: http://c2.com/cgi/wiki?PythonPhilosophy

最後に、Library-design-OO(これは、SOLIDに浸透する考え方の学派です)で、ライブラリ作成者は次のことを行います。

  • ユーザーに利便性を提供します。つまり、Naive-OOステージで識別されたすべてのメソッドを実装します。
  • ただし、内部的には、あるメソッドを別のメソッドで実装できる場合、最初のメソッドは、タスクを実行するために他のメソッドを呼び出す(つまり「委譲する」)だけです。
    • Naive-OOがメソッドを情報エキスパートではないクラスに配置する場合、Library-design-OOでは、メソッドは関連する情報エキスパートの他のメソッドを呼び出してタスクを実行します。
  • Pythonic思考を抑制します。 Pythonicの考えはコードライターの生活を簡単にしますライブラリの作成者として、重複しているように見えるすべての実装作業に注意を払い、ライブラリのユーザーの生活を容易にします。

「私の質問に答えていません。SRPに違反していますか?」

回答:ライブラリライターとしてコードをユーザーにリリースしていない場合、これはの決定的違反ではありません。

SOLIDの5つのガイドラインのうち4つ、つまりexceptingLiskov(LSP)は、ソフトウェアライブラリのリリース前の設計チェックリスト項目です。 (LSPは、基本的には論理( プロパティ(哲学) )に関連する正当性の要件であり、非論理的なソフトウェアを開発しない限り避けられません。)

ライブラリの作成者であるあなたは、コードの設計と実装を継続的に改善しながら、コードを維持することが許可されています refactoring(www.refactoring.com)

もちろん、オブジェクト、インターフェース、メソッド、実装などのコードを変更することができます。これは、ライブラリをリリースしてライブラリがユーザーを獲得するまで当てはまります。ライブラリユーザーが存在する場合、SOLID原則は、それらのライブラリユーザーのコードを壊さないようにするのに役立ちます。

したがって、あなたがまだプロセスのライブラリ設計段階にいるとすると、それがSRP違反であるかどうかを尋ねると、不確定な答え。

もちろん、プロトタイプコードがリリースされたときに違反に陥るかどうかに注意を払う必要があります。ただし、設計段階では、SRPによく関連する非常に舌のようなフレーズ、つまり、

クラスの変更理由は1つだけです。

残念ながら無関係です。
ソース(上記と同じ): http://www.oodesign.com/single-responsibility-principle.html

それを修正するには、代わりにこれを言うことができます:

SRPを満たすために、libraryクラスは、ライブラリが変更された後、可能な限り変更する理由が少ない(---)ように設計する必要がありますreleased


「私の質問の2番目の部分に答えてもらえますか:なぜですか?」

一番上までスクロールして、私の回答のすべての部分をもう一度読んでください。

1
rwong

あなたは2つのSOLID原則についての質問を持ち出しました。1つ目は単一の責任の原則についてです。

そして今、私はisAdjacentTo(Node)メソッドをGraphEdgeクラスに追加する必要があるのか​​、それともGraphUtil class(*)を作成して静的なedgeIsAdjacentToNode(Edge、Node)メソッドを追加する必要があるのか​​疑問に思っています。

これについて自問する必要があるのは、「ノードに隣接しているかどうかを調べるためにGraphEdgeクラスにクエリを実行するのは自然なことですか?」ということです。この質問に「はい」と答えることができれば、おそらくSRPに違反していません。それは本当にそれと同じくらい簡単です。

これらのクラスを設計している場合は、isAdjacentToメソッドをGraphEdgeクラスに配置します。他の何かとの関係についてクラスをクエリしています。私にとってそれはとても自然なことのように思えます。自然に感じられないのは、ユーティリティクラスで静的メソッドを呼び出して、すでに持っているオブジェクトに関する質問に答えることです。オブジェクト自体に聞いてみませんか?

私はユーティリティクラスをコードのにおいとして見る傾向があります。それらはときどきで使用されますが、99%の場合、既存のクラスに属するメソッドが含まれます。

2番目のSOLID先ほどお話しした原理は、開閉原理です。

また、私が最初のオプションに固執する場合、これはおそらく開閉原理に違反します。後でEdgeNodes(Node、Node)メソッドを追加する必要があるため、GraphEdgeは変更のために閉じていません。

クラスにメソッドを追加することは、クラスを変更することではなく、拡張することです。オープンクローズの原則は、クラスの内部機構がいじられることから保護することです。継承者が内部実装の詳細を変更できるようにしたくない。これは、変更のために閉じられることを意味します。クラスに機能を追加することとは関係ありません。これは、追加する機能が予期しない望ましくない外部の変更から保護されることを意味します。

1
Stephen