MVC・MVVM の比較

1. MVC・MVVM の概念まとめ
1-1. MVC(Model–View–Controller)
・Model:データ・ビジネスロジック
・View:UI(画面)
・Controller:UI のイベントを受けて Model を操作し、View を更新する。
・構造の特徴:「イベント主導」「Controller が UI を制御する」構造。
1-2. MVVM(Model–View–ViewModel)
・Model:データ・ビジネスロジック
・View:UI
・ViewModel:状態(state)とロジックを保持し、View がそれを監視して UI を自動更新する。
・構造の特徴:「状態主導」「ViewModel は UI を知らない」構造。
2. MVC・MVVM の共通点と違い
2-1. 共通点
| 項目 | 共通点 |
|---|---|
| 目的 | UI とロジックの分離による保守性向上 |
| Model の役割 | データ管理(API・DB・ロジック) |
| 大規模化対応 | コードの役割を分離して整理しやすくする |
| テスト容易性 | UI とロジックが分離されるためテストしやすい |
| 再利用性 | Model やロジックを別画面でも使い回しやすい |
2-2. 違い(MVC vs MVVM)
| 観点 | MVC | MVVM |
|---|---|---|
| 中央の役割 | Controller | ViewModel |
| UI との関係 | Controller が View を更新 | View が ViewModel を監視して更新 |
| 依存方向 | View ⇆ Controller | View → ViewModel(単方向) |
| イベント処理 | View の操作を Controller に渡す | View の操作を ViewModel のメソッドへ渡す |
| データ更新の仕組み | イベント主導 | 状態主導 |
| 双方向バインディング | 基本なし | あり(純粋な MVVM) |
| 適した規模 | 小〜中規模 | 中〜大規模に強い |
3. Flutter の MVVM と「純粋な MVVM」の違い
| 観点 | 純粋な MVVM(WPF / Android DataBinding など) | Flutter の MVVM(Provider / Riverpod) |
|---|---|---|
| バインディング | 双方向データバインディングあり(UI ⇆ ViewModel) | 双方向なし(状態 → UI のみ) |
| UI 定義形式 | XAML / XML | Dart コード |
| View と ViewModel の接続 | XAML の Binding 設定で自動 | Provider / Riverpod で手動紐付け |
| ViewModel の責務 | プロパティ + Command(UI 反応が前提) | 状態とロジック(UI を知らない) |
| コマンド概念 | ICommand が一般的 | コマンド概念はなく、メソッドを呼ぶだけ |
| フレームワーク依存 | 強い(WPF 依存・DataBinding 依存) | 弱い(状態管理パッケージ依存) |
| 更新方向 | 双方向バインディングで同期 | 単方向データフロー(React 式) |
| 画面と VM の関係 | 多くは 1 対 1 | 1 画面 1 VM または複数 VM など自由度が高い |
| 強制力 | フレームワークが MVVM を強制 | Flutter は MVVM を強制しない |
4. Flutter の MVVM は純粋ではないが良くないのか?
4-1. 結論
・結論:全く問題はなく、むしろ Flutter に最適化された「正しい形」である。
4-2. 理由
理由 1:Flutter は「双方向バインディング前提の UI フレームワーク」ではない
・React ベースの単方向データフロー(state → UI)が本質であり、MVVM を完全再現する必要はそもそもない。
理由 2:Flutter の UI はすべて Dart で構築される
・XAML のようなバインディング構文が不要であり、言語や仕組みが異なるため純粋 MVVM をそのまま持ち込む意味は薄い。
理由 3:Provider・Riverpod などの状態管理が「MVVM の ViewModel 役」として十分機能する
・状態管理と UI の自動再描画により、結果的に MVVM と同じ目的が達成される。
理由 4:純粋 MVVM の強制力(双方向バインディング)は複雑化の原因にもなる
・Flutter のように「状態 → UI」だけの方がシンプルで、バグも少なくなりやすい。
理由 5:Google も「Flutter は MVVM を公式に採用していない」
・Flutter は「自由な構成」で良く、UI フレームワークの思想(React 式)と相性が良いのは MVVM ライクな構成である。
4-3. 最終結論
・Flutter の MVVM は純粋 MVVM と異なるが、それが正しく、Flutter に最適化されたやり方である。
・品質的にもアーキテクチャ的にも問題はなく、むしろ自然な選択となる。
5. Flutter における MVVM のメリット・デメリット
5-1. メリット(Pros)
| メリット | 説明 |
|---|---|
| UI とロジックが明確に分離できる | View と ViewModel が別になるため、Widget がスリムになり保守しやすい。 |
| 状態管理(Provider / Riverpod)と相性が非常に良い | Flutter の UI は状態で描画されるため、MVVM の「状態中心」の設計がそのままはまる。 |
| テストがしやすい | ViewModel が UI を持たず、ロジックと状態を単体でテストできる。 |
| 画面ごとに責務を分割しやすい | 1 画面 1 ViewModel 構成にすると、チーム開発でも責務がぶれにくい。 |
| 再利用性が高い構造にできる | ViewModel をサービス化すると複数画面でロジックの共有ができる。 |
| UI の見通しが良くなる | Widget は UI 描画に専念し、ビジネスロジックが消えてすっきりする。 |
| コードの一貫性が作りやすい | プロジェクトの標準パターンとして導入しやすく、規模が大きくなっても破綻しにくい。 |
5-2. デメリット(Cons)
| デメリット | 説明 |
|---|---|
| 小規模アプリでは「過剰設計」になりやすい | ViewModel・Provider・Repository を分ける必要が出て、実装コストが増える。 |
| フォルダ構成が複雑になりがち | presentation / viewmodels / domain / data など階層が増える。 |
| 純粋な MVVM のように双方向バインディングはない | XAML のような自動同期はないため、UI から ViewModel への反映はメソッド呼び出しが必要。 |
| 状態管理の知識が必須 | Provider / Riverpod / BLoC などの理解が必要で、初心者にはハードルが高め。 |
| ViewModel が太りやすい | 正しく分割しないと、ロジックと状態が ViewModel に集中してしまう。 |
| パターンがカッチリ決まっていない | Flutter 公式の「MVVM 標準」がないため、各チームで流派が違い、混乱しやすい。 |
| 依存注入(DI)を併用すると複雑化しがち | get_it / Riverpod DI / json_serializable などと組み合わせると構造がさらに重くなる。 |
5-3. 総括:Flutter の MVVM が特に効果を発揮する場面
・Flutter の MVVM は、最小構成ではやや重く見える場合もあるが、規模が大きくなるほどメリットが大きい。
- 小〜中規模:過剰設計になりやすい。
- 中〜大規模:ロジック分離・テスト性・チーム開発で強さを発揮する。
- スタートアップや MVP 開発:MVVM ライト、シンプルな状態管理でも十分。
- 長期開発・拡張し続けるサービス:MVVM を採用する価値が高い。
6. Flutter × Riverpod MVVM の最小サンプル
この例では、カウンターの状態を ViewModel が管理し、View が watch して UI を更新するという最小の MVVM 例を示す。
6-1. ファイル構成(最小)
lib/
main.dart
counter/
counter_view.dart
counter_view_model.dart
6-2. counter_view_model.dart(ViewModel)
import 'package:flutter_riverpod/flutter_riverpod.dart';
// ViewModel の State(状態)
class CounterState {
final int count;
const CounterState(this.count);
}
// ViewModel(StateNotifier を使う)
class CounterViewModel extends StateNotifier<CounterState> {
CounterViewModel() : super(const CounterState(0));
void increment() {
state = CounterState(state.count + 1);
}
}
// Provider を公開
final counterViewModelProvider =
StateNotifierProvider<CounterViewModel, CounterState>(
(ref) => CounterViewModel());
6-3. counter_view.dart(View)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_view_model.dart';
class CounterView extends ConsumerWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// ViewModel の state を監視
final counterState = ref.watch(counterViewModelProvider);
return Scaffold(
appBar: AppBar(title: const Text("MVVM Counter (Riverpod)")),
body: Center(
child: Text(
"${counterState.count}",
style: const TextStyle(fontSize: 48),
),
),
floatingActionButton: FloatingActionButton(
// View → ViewModel のメソッド呼び出し
onPressed: () =>
ref.read(counterViewModelProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
6-4. main.dart(アプリ起動)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter/counter_view.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const CounterView(),
);
}
}
6-5. ポイント(MVVM としての最小要件)
・Model:今回は最小のため省略(本来は API 通信や Repository 層などを担当)。
・ViewModel:CounterViewModel が状態とロジックを管理し、UI の存在を知らない。
・View:状態の変更を ref.watch() で購読し、UI イベント(ボタン押下)から ViewModel のメソッドを呼び出す。

