MVC・MVVM の比較

MVVM
Flutter MVVM / MVC まとめ

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. ファイル構成(最小)

project_structure.txt
lib/
  main.dart
  counter/
    counter_view.dart
    counter_view_model.dart

6-2. counter_view_model.dart(ViewModel)

counter_view_model.dart
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)

counter_view.dart
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(アプリ起動)

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 のメソッドを呼び出す。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です