web-dev-qa-db-ja.com

単一責任パターンはクラスに対してどの程度具体的であるべきですか?

たとえば、コンソールとの間のあらゆる種類の入出力メソッドを持つコンソールゲームプログラムがあるとします。それらすべてを単一のinputOutputクラスに保持するか、startMenuIOinGameIOplayerIOgameBoardIOなどのより具体的なクラスに分類して、各クラスに約1〜5つのメソッドがあるようにするのが賢明でしょうか。

そして、同じメモで、それらを分解する方が良い場合は、IO名前空間に配置する方が賢明です。こうすることで、呼び出しを少し冗長にします。例:IO.inGameなど?

14
shinzou

更新(要約)

私はかなり冗長な答えを書いたので、すべてを要約すると次のようになります。

  • 名前空間は適切です。意味があるときはいつでも使用してください
  • inGameIOおよびplayerIOクラスを使用すると、SRPの違反となる可能性があります。これはおそらく、IOの処理方法とアプリケーションロジックを組み合わせていることを意味します。
  • ハンドラークラスで使用される(または共有される場合がある)いくつかの一般的なIOクラス)を用意します。これらのハンドラークラスは、生の入力をアプリケーションロジックが理解できる形式に変換します。
  • 同じことが出力にも当てはまります。これはかなり一般的なクラスでも実行できますが、ゲームの状態をハンドラー/マッパーオブジェクトに渡し、内部のゲーム状態を一般的なIOクラスが処理できるものに変換します。

あなたはこれを間違った方法で見ていると思います。アプリケーションのコンポーネントの機能でIOを分離しているのに対し、私にとっては、IOソース、およびIOの"type"

最初にいくつかの基本/汎用KeyboardIOクラスMouseIOを用意し、必要な場合と場所に基づいて、上記のIOを異なる方法で処理するサブクラスを用意します。 。
たとえば、テキスト入力は、ゲーム内コント​​ロールとは異なる方法で処理したいものです。特定のキーをそれぞれのユースケースに応じて異なる方法でマッピングしたいと思うかもしれませんが、そのマッピングはIO自体の一部ではなく、IOの処理方法です。

SRPにこだわって、キーボードIOに使用できるクラスがいくつかあります。状況に応じて、これらのクラスとは異なる方法でやり取りしたいと思うかもしれませんが、彼らの唯一の仕事は、ユーザーが何をしているのかを伝えることです。

次に、これらのオブジェクトを、生のIOを、アプリケーションロジックで使用できるものにマッピングするハンドラーオブジェクトに挿入します(例:ユーザーが"w")を押す =、ハンドラはそれをMOVE_FORWARD)にマッピングします。

これらのハンドラーは、次にキャラクターを移動させ、それに応じて画面を描画するために使用されます。かなり単純化しすぎですが、その要点は次のような構造です。

[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
   ||
   ==> [ Controls.Keyboard.InGameMapper ]

[ Game.Engine ] <- Controls.Keyboard.InGameMapper
                <- IO.Screen
                <- ... all sorts of stuff here
    InGameMapper.move() //returns MOVE_FORWARD or something
      ||
      ==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
          2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
          3. IO.Screen.draw();//generate actual output

現在、キーボードを担当するクラスIOそのままの形式です。このデータをゲームエンジンが実際に理解できるものに変換する別のクラス、このデータが使用されます関連するすべてのコンポーネントの状態を更新し、最後に、別のクラスが画面への出力を処理します。

すべての単一のクラスには単一のジョブがあります。キーボード入力の処理は、それが処理する入力が何であるかを知らない/気にしない/知っている必要があるクラスによって行われます。それがするすべては知っているhow入力を取得する(バッファリング、アンバッファリング、...)。

ハンドラーは、これをアプリケーションの残りの部分の内部表現に変換して、この情報を理解します。

ゲームエンジンは翻訳されたデータを取得し、それを使用して、関連するすべてのコンポーネントに何かが起こっていることを通知します。これらの各コンポーネントは、衝突チェックであろうと、キャラクターアニメーションの変更であろうと、1つのことだけを実行します。問題は、個々のオブジェクトごとです。

これらのオブジェクトは状態を中継し、このデータはGame.Screenに渡されます。これは、本質的には逆IOハンドラーです。これは、内部表現をIO.Screenコンポーネントが使用して、出力。

7

単一責任の原則は理解するのが難しい場合があります。私が便利だと思ったのは、文章の書き方のように考えることです。多くのアイデアを1つの文に詰め込もうとしないでください。各文は1つのアイデアを明確に述べ、詳細を延期する必要があります。たとえば、自動車を定義したい場合は、次のようにします。

通常は4つの車輪を備え、内燃機関を動力源とする路上走行車。

次に、「車両」、「道路」、「車輪」などを個別に定義します。あなたは言うつもりはありません:

舗装された、またはその他の方法で改良された2つの場所の間を大通り、ルート、または陸路で人を輸送するための車両で、車両の下に固定された車軸を中心に回転する4つの円形オブジェクトがあり、エンジンを搭載ガソリン、オイル、その他の燃料を空気で燃焼させることによる原動力。

同様に、クラス、メソッドなどをできるだけ簡単に中心的な概念を述べ、他のメソッドやクラスに詳細を委ねるようにする必要があります。文章を書くのと同じように、それらがどれくらい大きくなければならないかについて、難しい規則はありません。

23
Vaughn Cato

私が行く最善の方法は、それらを別々のクラスに保つことだと思います。少人数クラスは悪くありません。実際、ほとんどの場合、それは良い考えです。

あなたの特定のケースについては、分離することで、他のハンドラーに影響を与えることなく、それらの特定のハンドラーのロジックを変更するのに役立ち、必要に応じて、新しい入出力メソッドを追加するほうが簡単だと思います。

2
Zalomon

Single Responsibility Principalは、クラスが変更する理由は1つだけである必要があると述べています。クラスに変更する理由が複数ある場合は、クラスを他のクラスでは、この問題を解消するために構成を利用しています。

あなたの質問に答えるために、私はあなたに質問をしなければなりません:あなたのクラスは変更する理由が1つだけありますか?そうでない場合は、変更する理由が1つだけになるまで、特別なクラスを追加し続けることを恐れないでください。

0
Greg Burghardt