MVC・MVVM・MVP の違いを、データの流れで理解する

この 3 つは、よく「似ているけど何が違うのか」と並べて語られます。ですが目的は同じで、 UI 表示のロジックと、ドメイン(データとビジネスルール)を分けることです。 違うのは View と Model をどう仲介し、誰が誰に依存するかだけ。 まずはここを結論として押さえてください。

下のデモで、まったく同じ操作(名前を入力して「挨拶する」を押す)が、 3 つのパターンでどの登場人物をどの順に通って画面更新に至るかを、横並びで見比べられます。 線の向き(誰が誰を知っているか)に注目してください。

① 同じ操作を 3 パターンで流してみる

画面に表示される結果(まだ表示されていません)
ステップ 1 / 5

「次へ」で 1 ステップずつ進めて、操作が登場人物を渡り歩く様子を追えます。 MVC は Model の通知で View が再描画、MVP は Presenter が命令的に View を更新、 MVVM は バインディングで自動同期 —— この差が画面に出ます。

MVC

Model-View-Controller

Model の変更通知を View が受けて再描画する(古典 MVC)

View画面。入力を受け、表示する
Controller入力を受けて Model を更新する
Modelデータとビジネスルール。UI を知らない
依存方向
  • View Controller入力を渡す
  • Controller ModelModel を更新
  • Model View変更を通知
  • View ModelModel を観察して読む

1. ユーザーが名前を入力し、「挨拶する」を押す

MVP

Model-View-Presenter

Presenter が View を命令的に更新する(View は受動的)

View画面。入力を受け、表示する
PresenterView と Model を明示的に仲介する
Modelデータとビジネスルール。UI を知らない
依存方向
  • View Presenter操作を通知
  • Presenter ModelModel を操作
  • Model Presenter結果を返す
  • Presenter View表示を命令

1. ユーザーが名前を入力し、「挨拶する」を押す

MVVM

Model-View-ViewModel

データバインディングで View と ViewModel が自動同期する

View画面。入力を受け、表示する
ViewModelView 用の状態とコマンドを公開する
Modelデータとビジネスルール。UI を知らない
依存方向
  • View ViewModelバインディングで同期
  • ViewModel Viewバインディングで同期
  • ViewModel ModelModel を操作
  • Model ViewModel結果を返す

1. ユーザーが名前を入力する

なぜ View と Model を分けるのか(共通の動機)

3 つのパターンは、どれも同じ困りごとへの答えです。表示の都合とドメインのルールが 1 か所に 混ざると、画面のコードが膨らみ、テストしづらく、ちょっとした変更があちこちに波及します。 分けておけば、表示を直してもドメインは触らずに済み、ドメインのルールは画面なしで検証できます。

ここで Model はどのパターンでもほぼ同じ役割です。アプリのデータとビジネス ルールを持ち、UI のことを知りません。上のデモで「挨拶文を組み立てる」ロジック (buildGreeting)が Model 側に置かれ、3 パターンで共通して使えるのはそのためです。 違うのは Model の前に立つ仲介役の流儀だけ、という見方ができます。

分けない場合テンプレート(View)に分岐だらけのビジネスロジックを直書きし、View から DB を叩く。仲介役が全部を抱え込む。
分けた場合ドメインを Model に閉じ込め、UI 都合の状態を仲介役に置き、View は表示に徹する。仲介役を単体テストできる。

MVC(Model-View-Controller)

起源は Smalltalk-80 で、Trygve Reenskaug が考案したとされます。古典的な MVC の役割分担は こうです。

  • Controller:ユーザー入力(クリック等)を受け取り、Model を更新する。入力の受け口。
  • Model:データとビジネスルール。状態が変わると Observer(通知)で View に知らせる。
  • View:Model を観察して自分を再描画する。View が Model を直接読むことがある。

つまり古典 MVC では、View と Model のあいだに直接の線がありうるのが特徴です (View → Model の参照、Model → View の通知)。Controller はあくまで入力を受けて Model を 動かす係で、画面の更新そのものは Model の通知をきっかけに View 自身が行います。

古典 MVC と Web の「MVC」は別物として語られることがある

Rails・Laravel・Spring MVC などサーバーサイドの「MVC」は、古典 MVC と少しずれます。 そこでは「Controller がリクエストを受ける → Model を操作 → View(テンプレート)にデータを 渡して HTML を描画」という流れで、Model から View への Observer 通知はありません。 同じ名前でも前提が違うので、どちらの MVC を指しているかを意識すると混乱が減ります。

弱点もあります。Controller や View に責務が集まると 「Fat Controller」「Massive View Controller」(iOS 開発で有名な呼び名)になりやすく、 Model と View が直接結合するとテストしづらくなります。

MVP(Model-View-Presenter)

MVP の View は受動的(Passive View)です。View は「ボタンが押された」を Presenter に通知し、表示の指示(「このラベルにこの文字を出せ」)を Presenter から受けるだけで、 自分ではロジックを持ちません。

  • Presenter:View と Model の間を明示的に仲介する。Model を操作し、結果を View インターフェース越しに命令的に反映する(view.setGreeting("...") のように呼ぶ)。
  • View:インターフェースとして抽象化される。Presenter はその抽象に依存する。

View と Presenter は基本 1 対 1です。View が抽象(インターフェース)になっている おかげで、Presenter は View をモックに差し替えて単体テストできます。これが MVP の 大きな利点です。MVC との違いははっきりしていて、MVP では Model と View が直接やりとりしません(やりとりは Presenter 経由)。

グッドケースView をモックして Presenter のロジックを単体テストできる。表示の細部に縛られずにふるまいを検証できる。
バッドケースView が大きくなると Presenter インターフェースの呼び出しが膨大になり、ボイラープレートが増える。

MVVM(Model-View-ViewModel)

MVVM の ViewModel は、View に見せるための状態(プロパティ)とコマンドを公開します。 View の状態を保持しますが、View への参照は持ちません(View を知らない)。

View と ViewModel はデータバインディングで結ばれ、自動で同期します (双方向 or 単方向)。View でテキストが変われば ViewModel のプロパティが更新され、ViewModel が 変われば View が再描画されます。MVP のように view.setXxx() を命令的に呼ぶ必要が ありません。ここが MVP との決定的な違いです。MVP の Presenter は View を知っていて 命令的に更新しますが、MVVM の ViewModel は View を知らず、バインディングが宣言的につなぎます。

起源と代表例は、Microsoft の WPF / Silverlight(John Gossman が提唱)、Knockout.js など。 広い意味では Vue や Angular のリアクティブシステムも同じ系譜に置けます。

グッドケースバインディングで定型コードが減る。ViewModel を View なしで単体テストできる。
バッドケースバインディングが複雑になると追跡しづらい。ViewModel が肥大化する(Massive ViewModel)。

3 つの決定的な違い

違いは「View と Model をどう仲介するか」に集約されます。表で並べると、仲介役の種類と View 更新の仕組みが軸になっているのが分かります。

観点MVCMVPMVVM
仲介役ControllerPresenterViewModel
View ↔ Model の直接線ありうる(View が Model を観察)なし(Presenter が間に立つ)なし(ViewModel 経由)
View 更新の仕組みModel の変更通知で再描画Presenter が命令的に呼ぶデータバインディングで自動
View ↔ 仲介役の関係Controller は複数 View も扱える基本 1 対 1ViewModel は View を知らない
テスト容易性の勘所Model は可、View 結合が難View モックで Presenter を単体テストViewModel を単体テスト

View 更新の仕組みに注目すると流れがつかめます。MVC は Model の変更通知で View が自分を描き直し、MVP は Presenter が view.setXxx() で命令的に押し込み、 MVVM はバインディングが自動でつなぐ。命令的(MVP)から宣言的(MVVM)への移行が、この 3 つの 並びの分かりやすい流れです。

「Vue はどれ? React は?」

Vue はリアクティブなデータバインディングを持つので MVVM 寄りです。 ref / reactive が ViewModel 的、template が View、 composable や store が Model 的、という対応で見られます。テンプレートに値を書けば自動で 画面が同期するのは、まさに MVVM のバインディングの考え方です。

React は単方向データフロー+明示的な更新setState / フック)なので、3 つのどれにもきれいには収まりません。あえて当てはめれば、コンポーネントが View、state やフックが仲介役、という見方になりますが、無理に分類するより 「なぜ分離するのか」を見たほうが実りがあります。

分類より「なぜ分離するか」

どのフレームワークがどの分類か、を厳密に決めることにあまり意味はありません。 3 つに共通する狙いは 変更の波及を抑え、テストしやすくすること。 そのために View と Model を分け、仲介役を単体テストできる形にしておく —— ここが押さえどころです。

よくある誤解・バッドケース

  • テンプレートにビジネスロジックを直書き:View に if 分岐だらけの判定や、DB アクセスを書いてしまう。表示とドメインが混ざって、どちらのテストも難しくなる。
  • 仲介役が全部抱える:Controller / Presenter / ViewModel に計算もデータ取得も詰め込み、Model が空洞になる。Fat Controller / Massive ViewModel はこの形。
  • 名前だけで区別しようとする:仲介役の呼び名(Controller / Presenter / ViewModel)よりも、View 更新が通知なのか命令なのかバインディングなのか、View と Model に直接線があるかを見たほうが違いが分かる。

グッドケースは逆向きに考えれば出てきます。ドメインを Model に閉じ込め、UI 都合の状態だけを 仲介役に置き、View は表示に徹する。そして仲介役を単体テストできる状態にしておく —— 3 パターンに共通する良い形です。

まとめ

MVC・MVP・MVVM は、View と Model の仲介の仕方が違うだけで、Model の役割と 「分離して変更波及を抑える」という狙いは共通です。MVC は Model の通知で View が再描画、 MVP は Presenter が命令的に View を更新、MVVM はバインディングで自動同期。 View ↔ Model の直接線は古典 MVC にだけありえて、MVP / MVVM では仲介役が間に立ちます。

どの名前を選ぶかより、誰が誰を知っているか(依存方向)と、View 更新の仕組みを 把握しておくほうが実務では役に立ちます。デモの 3 パネルで線の向きを見比べた感覚を、 そのまま持ち帰ってもらえれば十分です。

出典・参考

  • Martin Fowler “GUI Architectures”(MVC / MVP / Passive View / Supervising Controller の整理)
  • Trygve Reenskaug — MVC(Smalltalk-80 起源)
  • Microsoft の MVVM 解説(John Gossman 由来)
  • iOS 開発における “Massive View Controller” の議論

本ページは上記の議論を、操作しながら学べる教材として再構成したものです。