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


16

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

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

この意味は:

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

つまり、GUIの変更とビジネスロジックの変更という2つの理由があります。

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

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

そのため、Controllerには2つの変更可能な理由があります。SRPを壊しますか?


2
コントローラーは双方向の道であり、典型的なトップダウンまたはボトムアップのアプローチではありません。コントローラは抽象化そのものなので、依存関係の1つを抽象化する可能性はありません。パターンの性質上、ここではSRPに準拠することはできません。要するに、はい、SRPに違反していますが、それは避けられません。
ジェロンバネベル

1
質問のポイントは何ですか?私たち全員が「はい、そうです」と答えたら、それではどうでしょうか?答えが「いいえ」の場合はどうなりますか?この質問で解決しようとしている本当の問題は何ですか?
ブライアンオークリー

1
「変更する理由」は「変更するコード」を意味しません。クラスの変数名に7つのタイプミスをした場合、そのクラスには7つの責任がありますか?いいえ。複数の変数または複数の関数がある場合、1つの責任しか持たない可能性もあります。
ボブ

回答:


14

その結果、SRPについて推論し続けると、「単一の責任」が実際に海綿状の用語であることに気付くようになります。人間の脳はどういうわけか異なる責任を区別することができ、複数の責任は1つの「一般的な」責任に抽象化できます。たとえば、一般的な4人家族で朝食を作る責任がある家族が1人いるとします。さて、これを行うには、卵とトーストパンをbでて、もちろん健康的な緑茶のカップを用意する必要があります(はい、緑茶が最適です)。このようにして、「朝食を作る」ことを小さな断片に分割し、それらを一緒に「朝食を作る」に抽象化できます。各ピースは、たとえば他の人に委任できる責任でもあることに注意してください。

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


1
追加されたメモのように。controller.makeBreakfast()とcontroller.wakeUpFamily()がある場合、そのコントローラーはSRPを壊していますが、それはコントローラーであるためではなく、複数の責任があるからです。
ボブ

答えてくれてありがとう、あなたをフォローしているのかどうかわからない。コントローラーに複数の責任があることに同意しますか?私がそう思う理由は、それが変化する2つの理由があると思うからです(私は思う):モデルの変化とビューの変化。これに同意しますか?
アビブコーン

1
はい、コントローラーに複数の責任があることに同意できます。しかし、これらは「低い」責任であり、最高の抽象化レベルでは1つの責任(言及した下位の責任を組み合わせたもの)のみであり、SRPに違反しないため、問題ではありません。
ヴァレリー

1
適切な抽象化レベルを見つけることは間違いなく重要です。「朝食を作る」例は良い例です。単一の責任を果たすには、多くの場合、達成しなければならないタスクがいくつかあります。コントローラーがこれらのタスクのみを調整している限り、SRPに従います。しかし、卵を沸騰させる、トーストを作る、またはお茶を醸造することについてあまりにも多くを知っている場合、それはSRPに違反するでしょう。
アラン

この答えは私にとって理にかなっています。バレンテリーありがとうございます。
J86

9

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

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

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

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

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


4

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

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

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

SRPに関するウィキペディアの記事から

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

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

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


1
コントローラーに次のようなメソッドを含めるべきだと思ったのspecificButtonsPressed()は、ビューがボタンや他のGUI要素の機能について何も知らないことを読んだからです。ボタンが押されたとき、ビューは単にコントローラーに報告し、コントローラーが「それが何を意味するか」を決定する(そしてモデルの適切なメソッドを呼び出す)ことを教えられました。ビュー呼び出しを行うcontroller.saveData()ということは、ビューが押されたという事実に加えて、このボタンを押すことの意味をビューが知る必要があることを意味します。
アビブコーン

1
ボタンを押すこととその意味を完全に分離するという考えを捨てると(コントローラーにのようなメソッドspecificButtonPressed()が含まれることになります)、実際にコントローラーはGUIにあまり結び付けられません。specificButtonPressed()メソッドを捨てるべきですか?これらの方法を持つことの利点は、あなたにとって理にかなっていますか?またはbuttonPressed()、コントローラーにメソッドを用意するのは問題になりませんか?
アビブコーン

1
言い換えると(長いコメントでごめんなさい):コントローラーにメソッドを持つことの利点は、ビューをボタンを押す意味から完全に分離することだと思います。ただし、欠点は、ある意味でコントローラーをGUIに結び付けることです。どのアプローチが良いですか?specificButtonPressed()
アビブコーン

@prog IMOコントローラーはビューを見えなくする必要があります。ボタンには何らかの機能がありますが、ボタンの詳細を知る必要はありません。コントローラーメソッドにデータを送信していることを知る必要があるだけです。その点では、名前は重要ではありません。呼び出すこともfoo、簡単に呼び出すこともできますfireZeMissiles。特定の機能に報告する必要があることのみを知っています。関数が何をするのかは、それを呼び出すことだけを知りません。コントローラーは、メソッドが呼び出されたときに特定の方法で応答するということだけで、メソッドがどのように呼び出されるかを気にしません。
シュライス

1

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

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

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

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

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

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

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


0

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

コントローラは、(例えば、文書の編集)モデルの状態を更新するためにモデルにコマンドを送信することができます。また、関連するビューにコマンドを送信して、モデルのビューの表示を変更できます(たとえば、ドキュメントをスクロールすることにより)。

source: wikipedia

代わりにRailsスタイルの「コントローラー」(アクティブなレコードインスタンスとダムテンプレートをジャグリング)を使用している場合、もちろんSRPが壊れています。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.