DI
is 何?
依存性注入 【DI】 Dependency Injection
- デザインパターンの一つ
- オブジェクト間に生じる依存関係をオブジェクト内のコードに直接記述せず、外部から何らかの形で与えるようにする手法
- 抽象に依存せよ、ということ
説明
DIを使わないパターン
設計例
実装例
car.dart
class NormalEngine {
bool start() {
return true;
}
void stot() {}
}
class Car {
late NormalEngine _engine;
Car() {
_engine = NormalEngine();
}
bool engineOn() {
return _engine.start();
}
engineOff() {
return _engine.stop();
}
}
car_test.dart
import 'package:flutter_test/flutter_test.dart';
void main() {
test('エンジンONに成功すること', () async {
final car = Car();
expect(car.engineOn(), true);
});
}
抱える課題
- エンジンONに失敗するテストがしんどい
DIを使うパターン
設計例
実装例
car.dart
abstract class Engine {
bool start();
void stot();
}
class Car {
final Engine _engine;
Car(this._engine);
bool engineOn() {
return _engine.start();
}
engineOff() {
return _engine.stop();
}
}
car_test.dart
import 'package:flutter_test/flutter_test.dart';
void main() {
test('エンジンONに成功すること', () async {
final car = Car(NormalEngine());
expect(car.engineOn(), true);
});
test('エンジンONに失敗すること', () async {
final car = Car(BrokenEngine());
expect(car.engineOn(), false);
});
}
class NormalEngine implements Engine {
@override
bool start() {
return true;
}
@override
void stot() {}
}
class BrokenEngine implements Engine {
BrokenEngine();
@override
bool start() {
return false;
}
@override
void stot() {}
}
嬉しいこと
- テスターがエンジン交換できるのでテスタビリティ向上
余談
FlutterでMockを使うテストはMokitoを使いましょう。
car_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'car_test.mocks.dart';
@GenerateMocks([Engine])
void main() {
late Engine _engine;
late Car _car;
setUp(() {
_engine = MockEngine();
_car = Car(_engine);
});
test('エンジンONに成功すること', () async {
when(_engine.start()).thenReturn(true);
expect(_car.engineOn(), true);
});
test('エンジンONに失敗すること', () async {
when(_engine.start()).thenReturn(false);
expect(_car.engineOn(), false);
});
}
Thanks
FlutterのNull safetyに対応したMockitoの基本的な使い方
Repository
is 何?
- デザインパターンの一つ
- データ操作に関連するロジックをビジネスロジックから切り離し、抽象化したレイヤに任せることで保守や拡張性を高める手法
- DIパターンを適用するのが慣例
設計例
Userというデータ(Entitiy)を扱う場合の例。
- ServiceはEntitiyの内容に関心がある。Entitiyの作られ方や保存場所に関心はない。
- UserRepositoryImplはRemote or Localのどちらを制御するかを判断するのが責務。Entitiyの作られ方に関心はない。
- UserModelは各DataSourceからのInputを元にEntityを生成する。
- 各DataSourceクラスはEntityに必要なデータを独自の手段で収集する。
実装例
user_repository.dart
class UserEntity {
final int id;
UserEntity(this.id);
}
abstract class UserRepository {
Future<UserEntity> getUser();
}
abstract class UserRemoteDataSource {
Future<UserEntity> fetch();
}
abstract class UserLocalDataSource {
Future<bool> exists();
Future<void> save(UserEntity userEntity);
Future<UserEntity> load();
}
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource _remoteDataSource;
final UserLocalDataSource _localDataSource;
const UserRepositoryImpl(this._remoteDataSource, this._localDataSource);
@override
Future<UserEntity> getUser() async {
UserEntity userEntity;
if (await _localDataSource.exists()) {
userEntity = await _localDataSource.load();
} else {
userEntity = await _remoteDataSource.fetch();
await _localDataSource.save(userEntity);
}
return userEntity;
}
}
class UserModel extends UserEntity {
UserModel(int id) : super(id);
factory UserModel.parse(String json) {
// Business Logic.
return UserModel(0);
}
}
class AndroidApi implements UserRemoteDataSource {
@override
Future<UserEntity> fetch() async {
// Business Logic.
return UserModel.parse('json');
}
}
class LocalCache implements UserLocalDataSource {
@override
Future<bool> exists() async {
// Business Logic.
return false;
}
@override
Future<UserEntity> load() async {
// Business Logic.
return UserModel.parse('json');
}
@override
Future<void> save(UserEntity userEntity) async {
// Business Logic.
}
}