はじめに
Flutterを網羅的に学習するにあたってRoadmapを使って学習を進めることにしました。
この記事では、Flutter初学者やこれからFlutterを学習し始める方に向けて、DIについてまとめています。
RoadmapはFlutterだけでなく、他の言語やスキルのロードマップも提供されており、何から学習して良いか分からないと悩んでいる方にとって有用なサイトになっています。
ぜひRoadmapを利用して学習してみてください。
Roadmapとは
簡潔に言えば、Roadmap.shは学習者にとってのガイドブックであり、学習の方向性を提供する学習ロードマップサイトです。
初心者から上級者まで、ステップバイステップでスキルを習得するための情報が提供されています。
学習の進め方が分かりやすく示されているだけでなく、個々の項目に参考資料やリソースへのリンクも提供されているので、学習者は目標を設定し、自分自身のペースで学習を進めることができます。
Dependency Injection
FlutterロードマップDependency Injectionでは以下の2つのサイトが紹介されています。興味のある方はぜひお読みください。
- Dependency Injection In Flutter: https://medium.com/flutter-community/dependency-injection-in-flutter-f19fb66a0740
- Flutter Dependency Injection For Beginners: https://www.youtube.com/watch?v=vBT-FhgMaWM
DIとは
DI(Dependency Injection / 依存性の注入)とは、ソフトウェア開発の設計パターンの一つで、端的に言うと、依存関係を取り除いて、実行時に外部から受け取るようにする手法のことです。特にオブジェクト指向プログラミングにおいて使用されます。
他のクラスを直接生成(インスタンス化)せず、外部からクラスの依存関係を注入することで、柔軟性やテスト容易性を向上させます。そのため、クラス間の結合度が低くなり、変更が生じた際にも影響範囲を最小限に抑えることができるのです。
DIの特徴
-
依存性の注入:
依存性を外部から注入するため、オブジェクトは自らの依存性に依存することはなく、外部から提供される依存性にのみ依存します。これによって、同じコンポーネントを異なる依存性で再利用することが容易にできます。 -
コードの疎結合性:
DIを用いることによって、コードが疎結合1になります。疎結合性が高いコードは変更が容易で、再利用性が向上します。 -
テスト容易性:
モックやスタブなどを用いて、依存性をテスト時に注入できるため、ユニットテストや統合テストが容易になります。 -
柔軟性と拡張性:
DIはプログラムの柔軟性と拡張性を向上させるので、新しい機能や変更が発生しても、変更のスコープを小さく抑えられます。 -
可読性と保守性:
DIによって外部から注入される依存性がコード内で直接記述されるため、コードの挙動が明確になり、可読性・保守性が向上します。 -
ライフサイクル管理:
DIは依存性のライフサイクル管理もサポートしているため、依存性がどのように生成・破棄されるかを管理することができます。
サンプルコード実装
Flutterで、DIを導入するためには、DIパッケージを利用することが一般的です。DIパッケージの代表的ものとして以下のようなパッケージがあります。
-
get_it:
シンプルなサービスロケーター2(Service Locator)パターンを実現するパッケージです。SingletonやFactoryをサポートしています。 -
provider:
Providerパターンを提供するパッケージで、DIの側面からも利用できます。ProviderというWidgetを提供しており、依存性の注入を簡単に行えます。 -
riverpod:
providerの改良版ともいえるパッケージで、状態管理と同時にDIもサポートしています。モダンで柔軟な設計が特徴です。 -
get:
シンプルなDIパッケージで、依存性の注入や状態管理が行いやすいように設計されています。
今回は riverpod
パッケージを使用してサンプルアプリを作成していきます。
以下のリンクからパッケージを追加して、 import
文を追加してください。
サンプルコード
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
// UserServiceのインターフェース
abstract class UserService {
String getUserName();
}
// UserServiceの実装クラス
class DefaultUserService implements UserService {
@override
String getUserName() {
return 'John';
}
}
// ユーザー名を表示するウィジェット
class UserNameWidget extends ConsumerWidget {
const UserNameWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Riverpodを使用してUserServiceの実装クラスのインスタンスを取得
final userService = ref.watch(userServiceProvider);
// UserServiceからユーザー名を取得
final userName = userService.getUserName();
// ユーザー名を表示
return Text('User Name: $userName');
}
}
// Riverpodプロバイダーを作成
final userServiceProvider = Provider<UserService>((ref) {
return DefaultUserService();
});
void main() {
runApp(
// RiverpodのProviderScopeを使用してアプリケーションをラップ
ProviderScope(
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Riverpod DI Example'),
),
body: const Center(
// UserNameWidgetを表示
child: UserNameWidget(),
),
),
),
),
);
}
-
UserService インターフェース:
UserService
は、ユーザーに関する操作を行うためのインターフェースです。ここでは単純なgetUserName
メソッドが定義されています。 -
DefaultUserService クラス:
UserService
インターフェースを実装するためのクラスです。実際のユーザー情報を提供するために使用されます。getUserName
メソッドでは固定のユーザー名 "John" を返しています。 -
UserNameWidget ウィジェット:
ConsumerWidget
を継承したウィジェットで、UserService
を利用してユーザー名を取得し、それを画面に表示しています。-
watch
メソッドを使用してuserServiceProvider
を監視し、UserService
の実装クラスのインスタンスを取得します。 -
build
メソッド内でuserService.getUserName()
を呼び出してユーザー名を取得し、Text ウィジェットで表示します。
-
-
userServiceProvider プロバイダー:
Provider
を使用して、UserService
の実装クラスを提供するプロバイダーを作成しています。このプロバイダーの主な役割は、userServiceProvider
を他の部分で利用することによって、アプリケーション内のどこからでも同じUserService
の実装クラスにアクセスできるようにすることです。この方法を使用することで、DefaultUserService
以外の実装に切り替える際に、変更が必要な箇所を最小限に抑えることができます。 -
main 関数:
ProviderScope
でアプリケーションをラップしています。これにより、Provider
を利用した状態管理が可能になります。
まとめ
依存性注入(DI)は、自身が依存している他のオブジェクトやサービスを自ら作成するのではなく、外部から受け取る(依存関係を外部から受け取る)デザインパターンです。
- 依存性(Dependency): あるオブジェクトが別のオブジェクトやサービスに依存している状態。
- 依存性注入容器(DI Container): 依存性を解決し、オブジェクトやサービスを提供する仕組み。例えば、RiverpodやProviderなど。
- 注入(Injection): 依存性を注入するプロセス。通常はコンストラクタ経由で行われます。
DIを使用することのメリット
テスト容易性: 依存性を注入することで、単体テストやモックの適用が容易になります。
柔軟性: 依存性の注入によって、異なる実装の切り替えが簡単に行えるため、柔軟性を持つことができます。
保守性: 依存性が外部から注入されるため、堅牢性の高いコードが書きやすくなります。
注意点
適切な範囲での依存性注入が重要です。過度に依存性注入されたコードは逆にコードを複雑にする可能性があるので。依存性注入容器は必要なときにのみ使用し、適切に設計されたもののみを提供することが重要になります。
参考資料
-
疎結合
疎結合とは簡単に言うと、お互いの関わりが薄くて分けることが容易になっている状態のことです。各要素が互いに深く結びついてはおらず、一部分の変更が他の要素に影響を及ぼす度合いが小さいので、担当や責任の分担のしやすさ、不具合発生時の原因追及のしやすさなどで優れていますが、要素間の連携や通信にかかる負荷が大きく、性能などで不利になることがあります。疎結合とは反対に関係が密で分けることが難しい状態のことを密結合と言います。こちらに分かりやすい記事があったので、興味のある方はぜひ見てください。 ↩ -
サービスロケータ
プログラムを特定の実装に依存させずに動作させたいときに用いる実装手法の一つです。サービスロケーターを使用することで、依存性を管理して、柔軟性のあるプログラムを作成できます。こちらに分かりやすい記事があったので、興味のある方はぜひ見てください。 ↩