web-dev-qa-db-ja.com

MVC:コントローラーは単一責任の原則を破りますか?

単一責任原則では、「クラスには変更の理由が1つあるはずです」と述べています。

MVCパターンでは、コントローラーの役割はビューとモデルを仲介することです。これは、ビューがGUIでユーザーが行ったアクションをレポートするためのインターフェースを提供し(たとえば、ビューがcontroller.specificButtonPressed()を呼び出すことができるようにします)、データを操作するためにモデルの適切なメソッドを呼び出すことができます。または、その操作を呼び出します(例:model.doSomething())。

この意味は:

  • ユーザーのアクションを報告するための適切なインターフェースをビューに提供するために、コントローラーはGUIについて知る必要があります。
  • また、モデルの適切なメソッドを呼び出すことができるようにするために、モデルのロジックについても知る必要があります。

つまり、これには変更する2つの理由があります。GUIの変更とビジネスロジックの変更です。

GUIが変更された場合。新しいボタンが追加された場合、コントローラーが新しいメソッドを追加して、ビューがユーザーがこのボタンを押したことを報告できるようにする必要がある場合があります。

また、モデルのビジネスロジックが変更された場合、モデルで正しいメソッドを呼び出すために、コントローラーを変更する必要がある場合があります。

したがって、コントローラには変更する2つの理由があります。それはSRPを壊しますか?

16
Aviv Cohn

結果的にSRPについての理由を続けると、「単一の責任」は実際には海綿状の用語であることに気づくでしょう。私たちの人間の脳はどういうわけかさまざまな責任を区別することができ、複数の責任を1つの「一般的な」責任に抽象化できます。たとえば、一般的な4人家族で、朝食の作成を担当する家族のメンバーが1人いるとします。これを行うには、卵とトーストのパンを茹で、そしてもちろん、健康的な緑茶のカップを準備する必要があります(そうです、緑茶が最適です)。このようにして、「朝食を作る」は、一緒に「朝食を作る」に抽象化された小さな部分に分割できます。各部分は、たとえば、他の人に委任される。

MVCに戻ります。もしモデルとビューの間の仲介が1つの責任ではなく2つの責任である場合、これらの2つを組み合わせた上の次の抽象化レイヤーは何でしょうか。それが見つからない場合は、正しく抽象化していないか、まったくないため、問題はありません。そして、私はそれがコントローラーとビューとモデルを扱うケースに当てはまると感じています。

14
valenterry

クラスに「変更する可能性のある2つの理由」がある場合、はい、SRPに違反しています。

通常、コントローラーは軽量で、GUI駆動のイベントに応答してドメイン/モデルを操作する単一の責任を負う必要があります。これらの各操作は、基本的にはユースケースまたは機能と考えることができます。

GUIに新しいボタンが追加された場合、コントローラーは、その新しいボタンが新しい機能を表す場合にのみ変更する必要があります(つまり、画面1に存在するが画面2にはまだ存在しない同じボタンとは対照的であり、その後、画面に追加2)。この新しい機能/機能をサポートするには、モデルにも対応する新しい変更が必要です。コントローラーは、GUI駆動のイベントに応答してドメイン/モデルを操作する責任を負っています。

バグが修正されたためにモデルのビジネスロジックが変化し、コントローラーの変更が必要な場合、これは特殊なケースです(またはモデルが開閉されたプリンシパルに違反している可能性があります)。モデルのビジネスロジックが新しい機能や機能をサポートするように変更された場合、コントローラーがその機能を公開する必要がある場合に限り、コントローラーに影響を与える必要はありません(ほとんどの場合そうです)。ドメインモデル(使用しない場合)。したがって、この場合、GUI駆動のイベントに応じてドメインモデルをこの新しい方法で操作できるように、コントローラーも変更する必要があります。

たとえば、永続化レイヤーがフラットファイルからデータベースに変更されたためにコントローラーを変更する必要がある場合、コントローラーは確実にSRPに違反しています。コントローラーが常に抽象化の同じレイヤーで機能する場合、それはSRPの達成に役立ちます。

9
jordan

コントローラはSRPに違反していません。あなたが述べているように、その責任はモデルとビューの間を仲介することです。

そうは言っても、この例の問題は、コントローラーメソッドをビューのロジック、つまりcontroller.specificButtonPressedに関連付けていることです。このようにメソッドに名前を付けると、コントローラーがGUIに結び付けられ、適切に抽象化できませんでした。コントローラは特定のアクション、つまりcontroller.saveDataまたはcontroller.retrieveEntryを実行する必要があります。 GUIに新しいボタンを追加しても、コントローラーに新しいメソッドが追加されるとは限りません。

ビュー内のボタンを押すとは、何かを実行することを意味しますが、他の方法で、またはビューを介さずに、簡単にトリガーできるものは何でも実行できます。

WikipediaのSRPに関する記事より

マーティンは責任を変更する理由として定義し、クラスまたはモジュールには変更する理由が1つだけ必要であると結論付けています。例として、レポートをコンパイルして印刷するモジュールを考えます。このようなモジュールは、2つの理由で変更できます。まず、レポートの内容が変更される可能性があります。次に、レポートの形式が変更される可能性があります。これら2つのことは、非常に異なる原因によって変化します。 1つは実体的、もう1つは化粧品です。単一の責任原則は、問題のこれらの2つの側面は実際には2つの別個の責任であるため、別個のクラスまたはモジュールにあるべきであると述べています。異なる時期に異なる理由で変化する2つのものを組み合わせるのは、悪い設計です。

コントローラは、そのメソッドの1つが呼び出されて指定されたデータをビューに提供する場合にのみ、ビューの内容に関係しません。モデルが持つメソッドを呼び出す必要があることがわかっている限り、モデルの機能を知る必要があるだけです。それ以外は何も知りません。

オブジェクトに呼び出し可能なメソッドがあることを知ることは、その機能を知ることと同じではありません。

4
Schleis

コントローラの単一の責任は、ビューとモデルの間を仲介する契約であることです。ビューは表示のみを担当し、モデルはビジネスロジックのみを担当します。 これら2つの責任を橋渡しするのはコントローラーの責任です。

それはすべて順調で良いことですが、学界から少し離れてベンチャーすることです。 MVCのコントローラーは、通常、多くの小さなアクションメソッドで構成されます。これらのアクションは、一般に、あることができることに対応します。製品を販売している場合は、おそらくProductControllerを使用します。そのコントローラーには、GetReviews、ShowSpecs、AddToCartなどのアクションがあります...

ビューには、UIを表示するSRPがあり、そのUIの一部にはAddToCartを示すボタンが含まれています。

コントローラには、プロセスに関与するすべてのビューとモデルを認識するSRPがあります。

コントローラのAddToCartアクションには、アイテムがカートに追加されたときに関与する必要があるすべての人を知るという特定のSRPがあります。

製品モデルには製品ロジックをモデル化するSRPがあり、ShoppingCartモデルには後でチェックアウトするためにアイテムを保存する方法をモデル化するSRPがあります。ユーザーモデルには、カートにアイテムを追加するユーザーをモデル化するSRPがあります。

ビジネスを遂行するためにモデルを再利用することは可能であり、またそうすべきであり、それらのモデルはコードのある時点で結合する必要があります。コントローラは、カップリングが発生するそれぞれの方法を制御します。

1
WhiteleyJ

コントローラーには、実際には1つの責任しかありません。ユーザーの入力に基づいてアプリケーションの状態を変更することです。

controllerは、モデルにコマンドを送信して、モデルの状態を更新できます(ドキュメントの編集など)。また、関連するビューにコマンドを送信して、モデルのビューの表示を変更することもできます(ドキュメントをスクロールするなど)。

source: wikipedia

代わりにRailsスタイルの「コントローラー」(アクティブレコードインスタンスとダムテンプレートを操作する)を使用している場合は、もちろんSRPに問題があります。

繰り返しになりますが、Railsスタイルのアプリケーションは、そもそもMVCではありません。

0
mefisto