MVC(Model-View-Controller)は、アプリケーションをデータと処理(Model)、画面表示(View)、入力の受け付け(Controller)の3つに分ける設計パターンです。MVVMやMVPはMVCから派生したパターンで、3つの違いは、表示更新の経路、画面状態の置き場所、Viewへの依存関係を比べると整理できます。この記事では、MVCの構成と処理の流れを押さえたうえで、MVVM・MVPとの違いを比較表と同じカウンター機能の3実装で示し、プラットフォームごとの使い分けまで整理します。
まとめ
- MVCは、Controllerがユーザーの入力を受けてModelを更新し、Modelの変更がViewに反映される構成です。1978年にTrygve ReenskaugがXerox PARCで考案しました。
- MVPのPassive View型は、PresenterがModelを更新したあと、Viewのメソッドを明示的に呼んで画面を書き換えます。Viewは受け身で、Presenterは偽のViewを渡せばテストできます。
- MVVMは、ViewModelが画面用の状態を持ち、データバインディングがその変化をViewへ運びます。ViewModelはViewを参照しないため、画面なしで状態だけをテストできます。
- 「Controller」の意味は、1978年の原典、Smalltalk-80、WebのModel 2、AppleのCocoaで異なります。解説記事ごとにMVCの説明が食い違うのはこのためです。
- 使い分けは、サーバーサイドWebならMVC、WPF・.NET MAUI・Flutter・AndroidならMVVM系、既存のUIKitやWinFormsで画面ロジックをテストしたいならMVPが目安です。
以下、MVCの基本から順に、違いの根拠とコードを示します。
MVCの構成要素と処理の流れ
MVCは、画面を持つアプリケーションの責務を3つに分けます。
| 要素 | 担当 | 例(ショッピングサイト) |
|---|---|---|
| Model | データと業務ルール | 商品、在庫、合計金額の計算 |
| View | 表示 | 商品一覧のHTML、カート画面 |
| Controller | 入力の受け付けと振り分け | 「カートに追加」の処理 |
入力から表示更新までの概略は「入力 → Controller → Model → View」ですが、これは依存関係や通信方向をすべて表した図ではありません。ユーザーがボタンを押すと、Controllerがその操作を受け取ってModelを更新し、更新後のModelの内容をViewが表示します。Modelは画面の存在を知らないので、同じ在庫データを一覧画面と管理画面の両方に出す、といった再利用ができます。
ただし、ModelからViewへ変更をどう伝えるかは実装によって違います。デスクトップGUIのMVCではViewがModelを監視し、変更通知(Observerパターン)を受けて自分で描画し直します。WebのMVCでは、リクエストのたびにControllerがModelからデータを取り出し、テンプレート(View)に渡してHTMLを組み立てます。後者を採るのがRuby on Rails、Spring MVC、ASP.NET Core MVCなどのWebフレームワークです。
MVC・MVP・MVVMの違いを比較表で整理
3つのパターンは、Modelを画面から切り離す点では同じです。比較する軸は、入力処理の担当、画面状態の置き場所、表示更新の仕組みです。以下の表ではMVCの表示更新を古典的GUI版、MVPをPassive View型として示します。採用先に挙げるRailsやSpring MVCは、Controllerが表示用データをViewへ渡すWeb版MVCです。
| 比較軸 | MVC | MVP | MVVM |
|---|---|---|---|
| 仲介役 | Controller | Presenter | ViewModel |
| ユーザー入力の受け手 | Controller | View(Presenterへ転送) | View(コマンドでViewModelへ) |
| 画面を更新する主体 | View(Modelを監視) | Presenter(Viewを直接操作) | バインディング |
| 仲介役はViewを参照するか | 参照する実装が多い | インターフェース経由で参照 | 参照しない |
| 画面なしでのテスト | UI依存を分離すれば可能 | 偽のViewを渡して可能 | ViewModel単体で可能 |
| 代表的な採用先 | Rails、Spring MVC | 標準採用のFWは少ない | WPF、.NET MAUI、Flutter |
MVCとMVVMの違いは、画面を更新するコードを誰が書くかです。MVCではViewがModelの変更通知を受けて描画するか、Controllerが描画を指示します。MVVMでは、ViewModelのプロパティとViewの部品をバインディングで結び、値が変わると画面が自動で追従します。ViewModelには「どの部品に何を表示するか」という処理が無くなります。
MVCとMVPの違いは、Viewの独立性です。MVPのPassive View型では、ViewはModelを参照せず、Presenterの指示で表示を更新します。Supervising Controller型では、単純な表示更新をViewとModelの結び付けに任せます。PresenterはViewをインターフェース越しに扱うので、テストでは本物の画面の代わりに記録用の偽物を渡せます。
同じカウンター機能を3パターンで実装したコード
ボタンを押すと数値が1増えるカウンターで、MVC・MVP・MVVMの表示更新経路を比較します。MVPとMVVMの例では独立したModelを省略し、カウントをPresenterまたはViewModelに保持しています。業務データと画面状態の分離まで比較する例ではありません。画面部品の代わりにconsole.logで表示を再現しているので、Node.jsでそのまま実行できます(Node.js v26.5.0で実行し、コメントの出力を確認)。
MVC:ViewがModelの変更通知を受けて描画
// mvc.mjs
class CounterModel {
#count = 0;
#listeners = [];
subscribe(fn) { this.#listeners.push(fn); }
increment() { this.#count++; this.#listeners.forEach(fn => fn(this.#count)); }
get count() { return this.#count; }
}
class CounterView {
constructor(model) { model.subscribe(n => this.render(n)); }
render(n) { console.log(`MVC view: ${n}`); }
}
class CounterController {
constructor(model) { this.model = model; }
onClick() { this.model.increment(); }
}
const model = new CounterModel();
new CounterView(model);
new CounterController(model).onClick();
// 出力: MVC view: 1
ControllerはModelを更新するだけで、画面には触れません。ViewはModelに登録した通知関数で描画します。Smalltalk-80やデスクトップGUIで使われてきた形です。
MVP:PresenterによるViewメソッドの直接呼び出し
// mvp.mjs
class CounterPresenter {
#count = 0;
constructor(view) { this.view = view; }
onClick() { this.#count++; this.view.showCount(this.#count); }
}
const view = {
showCount(n) { console.log(`MVP view: ${n}`); },
};
const presenter = new CounterPresenter(view);
presenter.onClick();
// 出力: MVP view: 1
Viewが持つのはshowCountという表示用のメソッドだけです。表示のタイミングも内容も、Presenterが決めます。
MVVM:バインディングによるViewModelの状態の表示反映
// mvvm.mjs
class CounterViewModel {
#count = 0;
#onChange = [];
get count() { return this.#count; }
set count(v) { this.#count = v; this.#onChange.forEach(fn => fn('count', v)); }
onPropertyChanged(fn) { this.#onChange.push(fn); }
increment() { this.count++; }
}
function bind(vm, render) {
vm.onPropertyChanged((name, value) => render(value));
}
const vm = new CounterViewModel();
bind(vm, n => console.log(`MVVM view: ${n}`));
vm.increment();
// 出力: MVVM view: 1
ViewModelはcountの値を変えて変更を通知するだけで、表示先を知りません。bind関数がWPFのバインディングやFlutterの再描画の役を担っています。実際のフレームワークでも状態と表示の接続は必要です。FlutterではListenableBuilderなどで変更を購読し、WPFではバインディングを設定します。変更通知の実装には、WPFではINotifyPropertyChangedインターフェースを使います。.NETのCommunityToolkit.Mvvm(2026年9月時点の最新安定版は8.4.2)なら、フィールドに付けた[ObservableProperty]属性から変更通知付きのプロパティがソース生成されます。
テストコードで見るPresenterとViewModelの差
// test.mjs
import assert from 'node:assert/strict';
class CounterPresenter {
#count = 0;
constructor(view) { this.view = view; }
onClick() { this.#count++; this.view.showCount(this.#count); }
}
class CounterViewModel {
#count = 0;
#onChange = [];
get count() { return this.#count; }
set count(v) { this.#count = v; this.#onChange.forEach(fn => fn('count', v)); }
onPropertyChanged(fn) { this.#onChange.push(fn); }
increment() { this.count++; }
}
// MVP: 偽のViewを渡し、Presenterが呼んだ内容を記録して検証する
const calls = [];
new CounterPresenter({ showCount: n => calls.push(n) }).onClick();
assert.deepEqual(calls, [1]);
// MVVM: Viewを用意せず、ViewModelの状態だけを検証する
const vm = new CounterViewModel();
vm.increment();
vm.increment();
assert.equal(vm.count, 2);
console.log('ok');
// 出力: ok
MVPのテストは「Presenterが正しい値でViewを呼んだか」を、MVVMのテストは「ViewModelの状態が正しいか」を確かめます。このMVCの例も、Viewを生成せずにControllerを呼び出し、Modelのcountが1になることを検証できます。単体テストの難しさは、パターン名だけでなくUIや外部処理への依存によって変わります。差が出るのは表示用の判断です。「在庫が0なら購入ボタンを無効にする」といった判断をMVCのView側に書くと、画面部品なしでは検証できません。MVPとMVVMは、この判断をPresenterやViewModelへ移して、画面なしでテストできるようにしたパターンです。
MVPとMVVMの成り立ち:MVCから何を変えた派生か
MVP:Taligentの論文とFowlerによる2分割
Martin Fowlerの「GUI Architectures」(2006年7月18日公開)によると、MVPは1990年代にIBMとTaligentで登場し、TaligentのMike Potelが1996年に書いた論文「MVP: Model-View-Presenter The Taligent Programming Model for C++ and Java」で広く知られるようになりました。Potelはこの論文で、MVPをSmalltalkの古典的なMVCをC++とJava向けに一般化したものと位置づけています。Fowler自身も2004年に「Model View Presenter」をパターンとして公開しましたが、2006年7月11日に「分割する必要がある」としてこれを退役させ、次の2つに分けています。
- Supervising Controller:単純な表示はViewがModelとの宣言的な結び付けで行い、複雑な場合だけPresenterが介入する。
- Passive View:Viewの操作をすべてPresenterが行う。Fowlerは、テストのしやすさを追求する中で生まれた形で、元のMVPの説明には無かったと書いています。
前の章のMVPのコードはPassive Viewにあたります。「MVP」と一口に言っても、Viewがどこまで自分で描画するかは実装によって幅があります。
MVVM:Presentation ModelからWPFへ
Fowlerは2004年7月19日に「Presentation Model」を公開し、のちに「このパターンはMVVMと呼ばれることが増えた」と追記して、別名にMVVMを挙げています。画面の状態と振る舞いを画面部品から独立したクラスに持たせる考え方で、ViewModelの原型です。MVVMという名前は、MicrosoftのJohn Gossmanが2005年10月のブログ記事「Introduction to Model/View/ViewModel pattern for building WPF apps」で紹介しました。GossmanはMVVMを、ViewをXAMLなどの宣言的な形式とデザインツールでデザイナーが作る環境に合わせたMVCの改良として説明しています。業務ロジックを書く開発者とは、道具も担当者も違うという前提です。そのうえでMVVMは「データバインディングの汎用的な仕組み」に依存すると書いています。WPFがこの仕組みを標準で備えていたため、MVVMはWPFとその後継の.NET系UIで定着しました。MVVM自体の仕組みやViewModelの設計はMVVMの解説記事で詳しく扱っています。
Controllerの役割の違い:原典・Smalltalk-80・Web・Cocoa
「MVCではViewがModelを直接参照する」「いや、ViewとModelはControllerを介してだけやり取りする」。解説記事によってMVCの説明が正反対になるのは、どの時代のMVCを指しているかが違うからです。
| 系統 | 時期 | Controllerの役割 | ViewとModel |
|---|---|---|---|
| Reenskaugの原典 | 1978〜1979年 | 複数のViewを生成し束ねる | ViewがModelを表示 |
| Smalltalk-80 | 1980年代 | マウスとキーボードの入力処理 | ViewがModelを監視 |
| Web(Model 2) | 1990年代末〜 | HTTPリクエストの処理 | ControllerがModelの値をViewへ渡す |
| Apple Cocoa | Mac OS X以降 | ViewとModelの仲介 | Controller経由で変更を伝達 |
原典を書いたTrygve Reenskaugは、自身のサイトで経緯を説明しています。1978年にXerox PARCで最初の実装と最初のMVCノートを書き、当初の用語はModel・View・Controller・Editorの4つでした。1979年5月12日のノートでは「Thing-Model-View-Editor」、Adele Goldbergとの議論を経た1979年12月10日のノートで「Models-Views-Controllers」という名前に落ち着いています。原典のControllerは配下のViewを生成して調整する役で、マウスやキーボードとのやり取りはEditorという別要素が担っていました。
その後、Reenskaugが離れたあとでJim AlthoffらがSmalltalk-80のクラスライブラリにMVCを実装しました。Reenskaugは「AlthoffはControllerという語を自分とはやや違う意味で使っている」と書いています。Smalltalk-80のControllerは入力処理の担当で、原典のEditorに近いものです。一般に知られている「Controllerが入力を受け、ViewはModelを監視する」MVCは、このSmalltalk-80版です。
WebのMVCはさらに別物です。従来型のサーバーレンダリングは、リクエストごとにHTMLを生成して返したところで処理を終えるため、サーバー側のViewがModelを監視し続けて画面を描き直す構成は取りません(Server-Sent EventsやWebSocketで更新を配信する構成は別の設計です)。Controllerがリクエストを受けてModelを呼び、結果をテンプレートに渡す一方向の流れになります。RailsやSpring MVCではControllerが表示用データをViewへ渡します。ただし、Railsのテンプレートは渡されたModelオブジェクトの属性を参照できるため、ViewとModelが直接やり取りしないとは限りません。
AppleのCocoaのMVCも独自の形です。Appleの設計ガイド(現在は「Retired Document」扱いのアーカイブ文書)は、Controllerを「1つ以上のViewと1つ以上のModelの間に立つ仲介役」と定義し、ViewとModelの変更はControllerを通って伝わるとしています。UIKitのViewControllerはこの系統です。
MVCについて調べて説明が噛み合わないときは、その記事がどの系統のMVCを前提にしているかを先に確かめてください。MVVMとの違いを論じる記事の多くはGUIのMVCを比較対象にしており、WebフレームワークのMVCにそのまま当てはめると話がずれます。
使い分けの判断基準:プラットフォームの標準との適合
3つのうちどれが優れているかではなく、まずは使うフレームワークの公式ガイドに沿い、UIの複雑さとテスト要件に応じて責務を分けることを勧めます。フレームワークの仕組みに逆らうと、バインディングや画面遷移の機能を自前で作り直すことになります。
| プラットフォーム | 標準的なパターン | 根拠 |
|---|---|---|
| Rails、Laravel、Spring MVC | MVC(Model 2) | フレームワークの構成そのもの |
| ASP.NET Core MVC | MVC(Model 2) | 公式ドキュメント |
| WPF、.NET MAUI | MVVM | XAMLのデータバインディング |
| Flutter | MVVM | 公式アーキテクチャガイド |
| Android(Jetpack) | ViewModel+単方向データフロー | 公式アーキテクチャガイド |
| iOS(UIKit) | MVC(Cocoa版) | Appleの旧設計ガイド |
WPFで作るデスクトップアプリなら、WPFのバインディングを前提にMVVMを選びます。Flutterでは公式アーキテクチャガイドがMVVMを採用しており、Riverpodと組み合わせた書き方はFlutterのMVVM実装で解説しています。
パターン選択で失敗しやすい場面
- 入力フォームが数個のサーバーレンダリング画面にMVVMを持ち込む:ViewModelとバインディングの層が増えるだけで、HTMLを返すWebのMVCより記述量が多くなります。
- UIKitのViewControllerに処理を集めすぎる:UIKitではViewControllerが画面の生成・イベント・通信を抱え込みやすく、1ファイルが数千行になる状態は「Massive View Controller」と揶揄されます。画面ロジックをPresenterやViewModelへ切り出すのが定番の対処です。
- Modelに業務ロジックを詰め込みすぎる:RailsやLaravelのMVCでは、Controllerを薄くした反動でModelが肥大化します。対処はRailsのFat Model対策を参照してください。
既存プロジェクトで課題がControllerへの処理集中に限られるなら、まずパターンを維持したまま画面ロジックをテスト可能な単位へ切り出すことを勧めます。MVCからMVVMへの移行を検討すべき条件はMVCモデルは古いのかを検討した記事でまとめています。
よくある質問
MVCモデルは古い・時代遅れですか?
古くはありません。Rails、Laravel、Spring MVC、ASP.NET Core MVCなど、サーバーサイドWebの主要フレームワークは2026年もMVCを採用しています。WPFや.NET MAUI、FlutterではMVVMに沿った設計が案内されていますが、デスクトップやモバイル全体が同じパターンに統一されたわけではありません。
C#やJavaでMVCを使うにはどのフレームワークを選びますか?
C#ではASP.NET Core MVC、JavaではSpring MVCが標準的な選択肢です。Spring Bootでは依存名がSpring Boot 4で変わっているので、Spring MVCの解説で確認してください。
MVCとDDD(ドメイン駆動設計)は両立しますか?
両立します。MVCは画面まわりの責務の分け方で、DDDはModelの内部、つまり業務ルールをどう設計するかの手法です。MVCのModel層の中身をドメイン駆動設計のエンティティや集約で組む、という重ね方になります。
MVVMのViewModelとMVCのControllerは何が違いますか?
Controllerは入力処理やModelとViewの調整を担いますが、画面状態を持つかどうかは実装によります。たとえばUIKitのViewControllerは、Viewの管理に必要な状態も扱います。ViewModelは「表示中の数値」「ボタンを押せるか」といった画面の状態そのものを持ち、Viewはそれをバインディングで表示します。
MVVMはどんな場面で向いていますか?
入力中の値や表示切り替えが多い画面で、画面ロジックを画面なしでテストしたい場面です。データバインディングを持つWPF、.NET MAUI、Flutterのように、フレームワークが仕組みを用意している環境で効果が大きくなります。