FlutterでDIとRepository

# DI

## is 何？

[依存性注入 【DI】 Dependency Injection](https://e-words.jp/w/%E4%BE%9D%E5%AD%98%E6%80%A7%E6%B3%A8%E5%85%A5.html)

* デザインパターンの一つ
* オブジェクト間に生じる依存関係をオブジェクト内のコードに直接記述せず、外部から何らかの形で与えるようにする手法
* 抽象に依存せよ、ということ

## 説明

### DIを使わないパターン

#### 設計例

[![alt](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9ApaaiBbPmJYnIgEPIK4ZDIy_CI_NFqz2qKaZApyz12AMbWSJAyZDJk6fXklpoYt8JSrp12i3QmSR2z8Bv-GKQcd1T85lqYZBJCqhqx50qSt4vfEQb0Dq30000)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuU9ApaaiBbPmJYnIgEPIK4ZDIy_CI_NFqz2qKaZApyz12AMbWSJAyZDJk6fXklpoYt8JSrp12i3QmSR2z8Bv-GKQcd1T85lqYZBJCqhqx50qSt4vfEQb0Dq30000)

#### 実装例

```dart: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();
  }
}
```

```dart:car_test.dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  test('エンジンONに成功すること', () async {
    final car = Car();
    expect(car.engineOn(), true);
  });
}
```

#### 抱える課題

* エンジンONに失敗するテストがしんどい

### DIを使うパターン

#### 設計例

[![alt](http://www.plantuml.com/plantuml/svg/POyx3i8m341tdy9Zk-G03AW2iS8PGkkMYDH6Zc41xWvDerA9zJqzVvfaNkq-He0s-fHmxnMVW4Wy1AOJL_K6poBnX_f-OWy97Rm10Xjfxrl2GzOvpaDNPJAvLVNIp9kEegEFSrNGJkLAN11CfpWDmyNS5WjSH-ZSQn4b_HVG47VV1py0)](http://www.plantuml.com/plantuml/svg/POyx3i8m341tdy9Zk-G03AW2iS8PGkkMYDH6Zc41xWvDerA9zJqzVvfaNkq-He0s-fHmxnMVW4Wy1AOJL_K6poBnX_f-OWy97Rm10Xjfxrl2GzOvpaDNPJAvLVNIp9kEegEFSrNGJkLAN11CfpWDmyNS5WjSH-ZSQn4b_HVG47VV1py0)

#### 実装例

```dart: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();
  }
}
```

```dart: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を使いましょう。

```dart: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の基本的な使い方](https://zuma-lab.com/posts/flutter-mockito-null-safety-unit-test)

# Repository

## is 何？

* デザインパターンの一つ
* データ操作に関連するロジックをビジネスロジックから切り離し、抽象化したレイヤに任せることで保守や拡張性を高める手法
* DIパターンを適用するのが慣例

## 設計例

Userというデータ（Entitiy）を扱う場合の例。

[![alt](http://www.plantuml.com/plantuml/svg/ZP9TIiKm48NViugi84kGU_NwS44HBE9pJ4Oxa6JAPEhFxfKI94q3xKjXJhzpECdabCMop34evOBDMG_0d-YWJ2yP-3u9oaybF9A7eE5R8FaLWac0tws3HNo6YJ8ASV4suZLEGUFV1o8aQVzkiY89NApOWMR-htTEx0dzUS9DKjzmVHgEmw_mTW1kcZwGis4JTRbjO_hSejvPDy8UhygYj34tzGvgkRdIzQWDuySeQtTdHSRJLofKU_OzlYtTowXxbBUbEjuUfawG_1p3Bm00)](http://www.plantuml.com/plantuml/svg/ZP9TIiKm48NViugi84kGU_NwS44HBE9pJ4Oxa6JAPEhFxfKI94q3xKjXJhzpECdabCMop34evOBDMG_0d-YWJ2yP-3u9oaybF9A7eE5R8FaLWac0tws3HNo6YJ8ASV4suZLEGUFV1o8aQVzkiY89NApOWMR-htTEx0dzUS9DKjzmVHgEmw_mTW1kcZwGis4JTRbjO_hSejvPDy8UhygYj34tzGvgkRdIzQWDuySeQtTdHSRJLofKU_OzlYtTowXxbBUbEjuUfawG_1p3Bm00)

* 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.
  }
}
```

## 参考

[インフラストラクチャの永続レイヤーの設計](https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design)
[実世界のAndroid、MVVM、およびリポジトリ](https://tirupati-tour-packages.com/ja/ja50817/)