デザインパターンとは
デザインパターン(設計パターン)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウに名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものです。
デザインパターンを利用するメリットは、再利用性の高い柔軟な設計ができるという点です。設計者の直感や経験などに依存していた設計が、デザインパターンを導入することで、初心者でも先人たちが詰め込んだ「知恵」を利用して設計をすることが可能になります。
もう一つのメリットとして、技術者どうしの意思疎通が容易になることが挙げられます。デザインパターンを習得している技術者どうしであれば、パターン名で設計の概要の合意を取ることが可能になります。このように、デザインパターンを学習しておくことで開発者どうしの意思疎通がスムーズになるのです。
以下は、Flutterにおける主要なデザインパターンです。
- Model-View-Controller (MVC)
- Model-View-ViewModel (MVVM)
- Provider(プロバイダー) パターン
- Bloc(ブロック) パターン
- Singleton(シングルトン) パターン
- Factory(ファクトリー) パターン
- Builder(ビルダー) パターン
- Composite(コンポジット) パターン
これらのパターンを駆使することで、開発者はアプリケーションの品質を向上させ、開発をスムーズに進めることができ、プロジェクトの規模やメンバーのスキルに応じた柔軟な設計が行えるようになります。
今回は MVC と MVVM に対象を絞って解説をしていきます。
Model-View-Controller
1. 定義
MVCは、ソフトウェア開発におけるデザインパターンの一つで、アプリケーションをモデル(Model)、ビュー(View)、コントローラ(Controller) の3つのコンポーネントに分割したものです。各コンポーネントは異なる責務を持ち、分離された形でアプリケーションを構築します。
2. コンポーネント
-
Model (モデル): データやビジネスロジックを管理し、それらの変更を通知する役割を担当します。ViewやControllerとは直接的な関係を持ちません。
-
View (ビュー): ユーザーに表示される情報やインターフェースを管理します。ユーザーからの入力はControllerに伝達されます。
-
Controller (コントローラ): ユーザーからの入力を処理し、それに基づいてModelやViewを変更します。ユーザーからの入力やコマンドをModelに変換します。
簡単に言うならば、Viewはディスプレイに表示される見た目部分を担当して、Controllerはゲームをする際、ユーザが手に持って扱うコントローラ部分を担当します。それに対してModelはViewとController以外の全ての部分を担当するのです。
UI(ユーザーインターフェース)
View と Controller をあわせて UI(ユーザーインターフェース)と呼びます。その名の通り、ユーザーとアプリケーションの両者をつなぐものです。
3. メリット
-
分離性: 各コンポーネントが独立しているため、変更が他のコンポーネントに影響を与えにくく、保守性が向上します。
-
再利用性: 同じModelやView、Controllerを異なるコンテキストで再利用することができます。
-
保守性: 各コンポーネントが特定の責務を持つため、コードの理解と保守が容易です。
4. サンプルコード
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterModel extends ChangeNotifier {
int get count => _count;
int _count = 0;
void increment() {
_count++;
notifyListeners();
}
void clear() {
_count = 0;
notifyListeners();
}
}
class CounterController {
const CounterController(CounterModel counter) : _counter = counter;
final CounterModel _counter;
void countUp() => _counter.increment();
void clear() => _counter.clear();
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterModel>();
final controller = context.read<CounterController>();
return Scaffold(
appBar: AppBar(
title: const Text('MVC Count App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Count',
style: Theme.of(context).textTheme.headlineLarge,
),
Text(
counter.count.toString(),
style: Theme.of(context).textTheme.headlineLarge,
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => controller.countUp(),
child: const Icon(Icons.add),
),
const SizedBox(height: 16),
FloatingActionButton(
onPressed: () => controller.clear(),
child: const Icon(Icons.clear),
),
],
),
);
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<CounterModel>(create: (_) => CounterModel()),
ProxyProvider<CounterModel, CounterController>(
update: (_, counter, __) => CounterController(counter),
),
],
child: const MaterialApp(
home: CounterPage(),
),
);
}
}
-
CounterModel クラス (Model):
-
CounterModel
クラスは、アプリケーションの状態を保持しています。ChangeNotifier
クラスを継承しており、notifyListeners()
メソッドを呼ぶことで変更通知を行います。 -
count
プロパティはカウンターの現在の値を取得します。 -
increment()
メソッドはカウンターを1つ増やし、clear()
メソッドはカウンターを0にリセットします。
-
-
CounterController クラス (Controller):
-
CounterController
クラスでは、CounterModel
クラスとやり取りするためのメソッドを定義します。 - コンストラクタで
CounterModel
インスタンスを受け取ります。 -
countUp()
メソッドはCounterModel
クラスのincrement()
メソッドを呼び出し、clear()
メソッドはCounterModel
クラスのclear()
メソッドを呼び出します。
-
-
CounterPage クラス (View):
-
CounterPage
クラスは、実際に画面に表示されるウィジェットを構築しています。 -
context.watch<CounterModel>()
を使用してCounterModel
クラスの変更を監視し、context.read<CounterController>()
を使用して、CounterController
クラスへのアクセスを取得しています。 - フローティングボタンを使用してカウントを増やすボタン、およびカウントをリセットするボタンのUIを構築しています。
-
Model-View-ViewModel
1. 定義
MVVM(Model-View-ViewModel)は、ソフトウェア開発におけるデザインパターンの一つで、主にUI関連のコードを整理し、テスト可能な構造にするためのデザインパターンです。MVVMは主にModel (モデル)、ViewModel (ビューモデル)、View (ビュー)の3つのコンポーネントから構成されます。
2. 主なコンポーネント
-
Model: アプリケーションのデータ構造やビジネスロジックを定義します。変更通知の仕組みを使用して、ViewModelに対して変更を通知します。データの変更通知や更新処理を提供することが一般的です。
-
View: ユーザーインターフェース(UI)を表現します。ユーザーの入力をViewModelに伝達し、ViewModelからデータ変更通知を受け取ります。
-
ViewModel: ユーザーインターフェースの状態や表示ロジックを管理します。ユーザーからの入力を処理し、Modelからデータを取得してUIに表示します。ModelとViewの間で情報のやり取りを仲介し、UIロジックの一元管理を行います。
3. メリット
-
切り離しとテスト容易性: Model、View、ViewModelが分離されているため、各コンポーネントを個別にテストしやすくなります。
-
再利用性: ViewModelの再利用が容易であり、同じViewModelを異なるViewで使用することができます。
-
データ バインディング: ViewModelの変更が自動的にViewに反映されるデータバインディングの仕組みがあるため、手動でUIを更新する必要が減ります。
-
UI ロジックの独立性: ViewModelがUIロジックを管理するため、ViewはUIの見た目だけを担当します。これによって、UIロジックの変更がUIコンポーネント(見た目)を変更せずに可能となります。
-
複数人での開発 MVVM は View と Model が完全に分離したアーキテクチャです。View と Model の間に依存関係はないので、 View の作成、Model の作成を複数人で担当し、並行して行うことが容易になります。
MVC と MVVM の大きな違いはView に反映するデータを保持する場所にあります。MVC では Controller が担当するのは操作のみで、データの保存は行いません、そのため Model の状態を View が確認し反映するアーキテクチャとなっています。
一方で、 MVVM では ViewModel が Model の状態を監視し、View が使いやすい状態に加工して保持します。これによって、View と Model が完全に分離したアーキテクチャとなります。
4. サンプルコード
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterModel {
int count = 0;
void increment() {
count++;
}
void decrement() {
count--;
}
}
class CounterViewModel extends ChangeNotifier {
final CounterModel _counterModel = CounterModel();
int get count => _counterModel.count;
void increment() {
_counterModel.increment();
notifyListeners();
}
void decrement() {
_counterModel.decrement();
notifyListeners();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(
create: (context) => CounterViewModel(),
child: CounterView(),
),
);
}
}
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counterViewModel = Provider.of<CounterViewModel>(context);
return Scaffold(
appBar: AppBar(
title: Text('MVVM Counter App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Count',
style: Theme.of(context).textTheme.headlineLarge,
),
Text(
'${counterViewModel.count}',
style: Theme.of(context).textTheme.headlineLarge,
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: counterViewModel.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
),
SizedBox(width: 10),
FloatingActionButton(
onPressed: counterViewModel.decrement,
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
],
),
);
}
}
-
CounterModel クラス (Model):
-
CounterModel
クラスはカウンターのデータとロジックを含むモデルです。count
プロパティでカウント値を保持し、increment
とdecrement
メソッドでカウントを増減させます。
-
-
CounterViewModel クラス (ViewModel):
-
CounterViewModel
クラスはChangeNotifier
を継承しており、モデルとビューをつなぐ役割を果たします。 -
_counterModel
インスタンスを内部に持ち、カウント値を取得するためのcount
ゲッターを提供します。 -
increment
とdecrement
メソッドは、モデルのメソッドを呼び出してカウントを変更し、notifyListeners
メソッドを呼び出してリスナー(ビュー)に変更を通知します。
-
-
CounterPage クラス (View):
-
ChangeNotifierProvider
を使ってCounterViewModel
を提供し、CounterView
ウィジェットを子として渡します。これにより、CounterView
内でCounterViewModel
が利用できるようになります。
-
まとめ
MVC は、Model、View、Controllerを明確に分離することで、アプリケーションの構造を整理し、保守性を高めることができます。一方で、MVVM は、ViewModelがUIロジックを管理することで、UIの変更に強く、テストしやすい設計を実現します。
どちらのパターンを選ぶべきかは、プロジェクトの規模、開発チームのスキル、アプリケーションの特性によって異なってくるため、状況に応じたデザインパターンを採用することが必要です。
MVC は、比較的シンプルなアプリケーションや、既存のMVCベースのフレームワークとの連携を考慮する場合に適しており、MVVM は、大規模なアプリケーションや、UIが複雑で頻繁に変わる可能性がある場合に適しています。
Flutter では、Provider
や Riverpod
などの状態管理ライブラリと組み合わせることで、MVVMパターンをより効果的に実装することができます。
MVCとMVVMの比較
特徴 | MVC | MVVM |
---|---|---|
分離 | Model、View、Controller | Model、View、ViewModel |
状態管理 | コントローラが状態を管理 | ビューモデルが状態を管理 |
データバインディング | 手動 | 自動 |
UIロジック | Controllerに含まれる | ViewModelに含まれる |
テスト容易性 | 比較的高い | 高い |
再利用性 | 比較的高い | 高い |
参考資料
告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。
みなさまからのご応募をお待ちしております。