1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FlutterでDIとRepository

Last updated at Posted at 2022-02-20

DI

is 何?

依存性注入 【DI】 Dependency Injection

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

説明

DIを使わないパターン

設計例

alt

実装例

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を使うパターン

設計例

alt

実装例

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)を扱う場合の例。

alt

  • 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.
  }
}

参考

インフラストラクチャの永続レイヤーの設計
実世界のAndroid、MVVM、およびリポジトリ

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?