From 6a2d86c83c58a26033188a0f9dce12365fd6c473 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 21 Apr 2021 08:05:38 +0300 Subject: [PATCH 01/14] implemented expiremental di with new api --- example/bin/main_experiment.dart | 82 +++++++++++++++++++++++++++++ lib/experimental/binding.dart | 53 +++++++++++++++++++ lib/experimental/di.dart | 1 + lib/experimental/factory.dart | 5 ++ lib/experimental/module.dart | 18 +++++++ lib/experimental/scope.dart | 88 ++++++++++++++++++++++++++++++++ 6 files changed, 247 insertions(+) create mode 100644 example/bin/main_experiment.dart create mode 100644 lib/experimental/binding.dart create mode 100644 lib/experimental/di.dart create mode 100644 lib/experimental/factory.dart create mode 100644 lib/experimental/module.dart create mode 100644 lib/experimental/scope.dart diff --git a/example/bin/main_experiment.dart b/example/bin/main_experiment.dart new file mode 100644 index 0000000..52bc4cf --- /dev/null +++ b/example/bin/main_experiment.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/experimental/module.dart'; + +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().withName("apiClient").toInstance(ApiClientMock()); + bind().withName("networkRepo").toProvide( + () => NetworkDataRepository( + currentScope.resolve(named: "apiClient"), + ), + ); + // .singeltone(); + bind().toProvide( + () => DataBloc( + currentScope.resolve(named: "networkRepo"), + ), + ); + } +} + +void main() async { + final scope = openRootScope().installModules([ + AppModule(), + ]); + + final dataBloc = scope.resolve(); + dataBloc.data.listen((d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), onDone: () => print('DONE')); + + await dataBloc.fetchData(); +} + +class DataBloc { + final DataRepository _dataRepository; + + Stream get data => _dataController.stream; + StreamController _dataController = new StreamController.broadcast(); + + DataBloc(this._dataRepository); + + Future fetchData() async { + try { + _dataController.sink.add(await _dataRepository.getData()); + } catch (e) { + _dataController.sink.addError(e); + } + } + + void dispose() { + _dataController.close(); + } +} + +abstract class DataRepository { + Future getData(); +} + +class NetworkDataRepository implements DataRepository { + final ApiClient _apiClient; + final _token = 'token'; + + NetworkDataRepository(this._apiClient); + + @override + Future getData() async => await _apiClient.sendRequest( + url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); +} + +abstract class ApiClient { + Future sendRequest({@required String url, String token, Map requestBody}); +} + +class ApiClientMock implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'hello world'; + } +} diff --git a/lib/experimental/binding.dart b/lib/experimental/binding.dart new file mode 100644 index 0000000..9f9c590 --- /dev/null +++ b/lib/experimental/binding.dart @@ -0,0 +1,53 @@ +enum Mode { SIMPLE, INSTANCE, PROVIDER_INSTANCE } + +class Binding { + late Mode _mode; + late Type _key; + late String _name; + late T _instance; + late T Function() _provider; + late bool _isSingeltone = false; + late bool _isNamed = false; + + Binding() { + _mode = Mode.SIMPLE; + _key = T; + } + + Mode get mode => _mode; + Type get key => _key; + String get name => _name; + bool get isSingeltone => _isSingeltone; + bool get isNamed => _isNamed; + + Binding withName(String name) { + _name = name; + _isNamed = true; + return this; + } + + Binding toInstance(T value) { + _mode = Mode.INSTANCE; + _instance = value; + _isSingeltone = true; + return this; + } + + Binding toProvide(T Function() value) { + _mode = Mode.PROVIDER_INSTANCE; + _provider = value; + return this; + } + + Binding singeltone() { + if (_mode == Mode.PROVIDER_INSTANCE) { + _instance = _provider.call(); + } + _isSingeltone = true; + return this; + } + + T? get instance => _instance; + + T? get provider => _provider.call(); +} diff --git a/lib/experimental/di.dart b/lib/experimental/di.dart new file mode 100644 index 0000000..533dda5 --- /dev/null +++ b/lib/experimental/di.dart @@ -0,0 +1 @@ +class DartDi {} diff --git a/lib/experimental/factory.dart b/lib/experimental/factory.dart new file mode 100644 index 0000000..79fedd0 --- /dev/null +++ b/lib/experimental/factory.dart @@ -0,0 +1,5 @@ +import 'package:dart_di/experimental/scope.dart'; + +abstract class Factory { + T createInstance(Scope scope); +} diff --git a/lib/experimental/module.dart b/lib/experimental/module.dart new file mode 100644 index 0000000..e5e0422 --- /dev/null +++ b/lib/experimental/module.dart @@ -0,0 +1,18 @@ +import 'dart:collection'; + +import 'package:dart_di/experimental/binding.dart'; +import 'package:dart_di/experimental/scope.dart'; + +abstract class Module { + final Set _bindingSet = HashSet(); + + Binding bind() { + final binding = Binding(); + _bindingSet.add(binding); + return binding; + } + + Set get bindingSet => _bindingSet; + + void builder(Scope currentScope); +} diff --git a/lib/experimental/scope.dart b/lib/experimental/scope.dart new file mode 100644 index 0000000..fbdd37a --- /dev/null +++ b/lib/experimental/scope.dart @@ -0,0 +1,88 @@ +import 'dart:collection'; + +import 'package:dart_di/experimental/binding.dart'; +import 'package:dart_di/experimental/module.dart'; + +Scope openRootScope() => Scope(null); + +class Scope { + final Scope? _parentScope; + + Scope? get parentScope => _parentScope; + + final Map _scopeMap = HashMap(); + + Scope(this._parentScope); + + final Set _modulesList = HashSet(); + + Scope openSubScope(String name) { + final subScope = Scope(this); + if (!_scopeMap.containsKey(name)) { + _scopeMap[name] = subScope; + } + return _scopeMap[name]!; + } + + void closeSubScope(String name) { + _scopeMap.remove(name); + } + + Scope installModules(List modules) { + _modulesList.addAll(modules); + modules.forEach((module) => module.builder(this)); + return this; + } + + Scope dropModules() { + _modulesList.removeAll(_modulesList); + return this; + } + + /** + * Возвращает найденную зависимость, определенную параметром типа [T]. + * Выдает [StateError], если зависимость не может быть разрешена. + * Если вы хотите получить [null], если зависимость не может быть найдена, + * то используйте вместо этого [tryResolve] + * @return - возвращает объект типа [T] или [StateError] + */ + T resolve({String? named}) { + var resolved = tryResolve(named: named); + if (resolved != null) { + return resolved; + } else { + throw StateError( + 'Can\'t resolve dependency `$T`. Maybe you forget register it?'); + } + } + + /** + * Возвращает найденную зависимость типа [T] или null, если она не может быть найдена. + */ + T? tryResolve({String? named}) { + // 1 Поиск зависимости по всем модулям текущего скоупа + if (_modulesList.isNotEmpty) { + for (Module module in _modulesList) { + for (Binding binding in module.bindingSet) { + if (binding.key == T && + ((!binding.isNamed && named == null) || + (binding.isNamed && named == binding.name))) { + switch (binding.mode) { + case Mode.INSTANCE: + return binding.instance; + case Mode.PROVIDER_INSTANCE: + return binding.isSingeltone + ? binding.instance + : binding.provider; + default: + return null; + } + } + } + } + } + + // 2 Поиск зависимостей в родительском скоупе + return _parentScope != null ? _parentScope!.tryResolve() : null; + } +} From b2b66bdcfd65fee65997bc448ef5599b9acca692 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 21 Apr 2021 08:12:23 +0300 Subject: [PATCH 02/14] fixed example --- example/bin/main_experiment.dart | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/example/bin/main_experiment.dart b/example/bin/main_experiment.dart index 52bc4cf..4799a1e 100644 --- a/example/bin/main_experiment.dart +++ b/example/bin/main_experiment.dart @@ -4,15 +4,25 @@ import 'package:dart_di/experimental/scope.dart'; import 'package:dart_di/experimental/module.dart'; class AppModule extends Module { + bool isMock; + + AppModule({required this.isMock}); + @override void builder(Scope currentScope) { - bind().withName("apiClient").toInstance(ApiClientMock()); - bind().withName("networkRepo").toProvide( + bind().withName("apiClientMock").toInstance(ApiClientMock()); + bind().withName("apiClientImpl").toInstance(ApiClientImpl()); + + bind() + .withName("networkRepo") + .toProvide( () => NetworkDataRepository( - currentScope.resolve(named: "apiClient"), + currentScope.resolve( + named: isMock ? "apiClientMock" : "apiClientImpl", + ), ), - ); - // .singeltone(); + ) + .singeltone(); bind().toProvide( () => DataBloc( currentScope.resolve(named: "networkRepo"), @@ -23,7 +33,7 @@ class AppModule extends Module { void main() async { final scope = openRootScope().installModules([ - AppModule(), + AppModule(isMock: false), ]); final dataBloc = scope.resolve(); @@ -77,6 +87,14 @@ class ApiClientMock implements ApiClient { @override Future sendRequest( {@required String? url, String? token, Map? requestBody}) async { - return 'hello world'; + return 'Local Data'; + } +} + +class ApiClientImpl implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Network data'; } } From c9ddc2ffa85f29b2f88991bab874bdd6e6f8d15d Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 21 Apr 2021 08:25:55 +0300 Subject: [PATCH 03/14] fixed resolve method --- example/bin/main_experiment.dart | 21 +++++++++++++++------ lib/experimental/scope.dart | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/example/bin/main_experiment.dart b/example/bin/main_experiment.dart index 4799a1e..fa2b968 100644 --- a/example/bin/main_experiment.dart +++ b/example/bin/main_experiment.dart @@ -4,15 +4,20 @@ import 'package:dart_di/experimental/scope.dart'; import 'package:dart_di/experimental/module.dart'; class AppModule extends Module { - bool isMock; - - AppModule({required this.isMock}); - @override void builder(Scope currentScope) { bind().withName("apiClientMock").toInstance(ApiClientMock()); bind().withName("apiClientImpl").toInstance(ApiClientImpl()); + } +} +class FeatureModule extends Module { + bool isMock; + + FeatureModule({required this.isMock}); + + @override + void builder(Scope currentScope) { bind() .withName("networkRepo") .toProvide( @@ -33,10 +38,14 @@ class AppModule extends Module { void main() async { final scope = openRootScope().installModules([ - AppModule(isMock: false), + AppModule(), ]); - final dataBloc = scope.resolve(); + final subScope = scope + .openSubScope("featureScope") + .installModules([FeatureModule(isMock: true)]); + + final dataBloc = subScope.resolve(); dataBloc.data.listen((d) => print('Received data: $d'), onError: (e) => print('Error: $e'), onDone: () => print('DONE')); diff --git a/lib/experimental/scope.dart b/lib/experimental/scope.dart index fbdd37a..dbf9dbb 100644 --- a/lib/experimental/scope.dart +++ b/lib/experimental/scope.dart @@ -83,6 +83,6 @@ class Scope { } // 2 Поиск зависимостей в родительском скоупе - return _parentScope != null ? _parentScope!.tryResolve() : null; + return _parentScope != null ? _parentScope!.tryResolve(named: named) : null; } } From 4302d733ba5886e082ba8d2534145e3ceaf35f89 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 22 Apr 2021 09:56:37 +0300 Subject: [PATCH 04/14] added documentation --- lib/experimental/binding.dart | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/experimental/binding.dart b/lib/experimental/binding.dart index 9f9c590..3ab9beb 100644 --- a/lib/experimental/binding.dart +++ b/lib/experimental/binding.dart @@ -1,5 +1,8 @@ enum Mode { SIMPLE, INSTANCE, PROVIDER_INSTANCE } +/// RU: Класс Binding настраивает параметры экземпляра. +/// ENG: The Binding class configures the settings for the instance. +/// class Binding { late Mode _mode; late Type _key; @@ -14,18 +17,50 @@ class Binding { _key = T; } + /// RU: Метод возвращает [Mode] экземпляра. + /// ENG: The method returns the [Mode] of the instance. + /// + /// return [Mode] Mode get mode => _mode; + + /// RU: Метод возвращает тип экземпляра. + /// ENG: The method returns the type of the instance. + /// + /// return [Type] Type get key => _key; + + /// RU: Метод возвращает имя экземпляра. + /// ENG: The method returns the name of the instance. + /// + /// return [String] String get name => _name; + + /// RU: Метод проверяет сингелтон экземпляр или нет. + /// ENG: The method checks the singleton instance or not. + /// + /// return [bool] bool get isSingeltone => _isSingeltone; + + /// RU: Метод проверяет именован экземпляр или нет. + /// ENG: The method checks whether the instance is named or not. + /// + /// return [bool] bool get isNamed => _isNamed; + /// RU: Добавляет имя для экземляпя [value]. + /// ENG: Added name for instance [value]. + /// + /// return [Binding] Binding withName(String name) { _name = name; _isNamed = true; return this; } + /// RU: Инициализация экземляпяра [value]. + /// ENG: Initialization instance [value]. + /// + /// return [Binding] Binding toInstance(T value) { _mode = Mode.INSTANCE; _instance = value; @@ -33,12 +68,20 @@ class Binding { return this; } + /// RU: Инициализация экземляпяра  через провайдер [value]. + /// ENG: Initialization instance via provider [value]. + /// + /// return [Binding] Binding toProvide(T Function() value) { _mode = Mode.PROVIDER_INSTANCE; _provider = value; return this; } + /// RU: Инициализация экземляпяра  как сингелтон [value]. + /// ENG: Initialization instance as a singelton [value]. + /// + /// return [Binding] Binding singeltone() { if (_mode == Mode.PROVIDER_INSTANCE) { _instance = _provider.call(); @@ -47,7 +90,15 @@ class Binding { return this; } + /// RU: Поиск экземпляра. + /// ENG: Resolve instance. + /// + /// return [T] T? get instance => _instance; + /// RU: Поиск экземпляра. + /// ENG: Resolve instance. + /// + /// return [T] T? get provider => _provider.call(); } From ed0c2fae53fa72beca2e461efd801cabf26a92c6 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 22 Apr 2021 10:10:34 +0300 Subject: [PATCH 05/14] added documentation --- lib/experimental/module.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/experimental/module.dart b/lib/experimental/module.dart index e5e0422..f760594 100644 --- a/lib/experimental/module.dart +++ b/lib/experimental/module.dart @@ -3,16 +3,39 @@ import 'dart:collection'; import 'package:dart_di/experimental/binding.dart'; import 'package:dart_di/experimental/scope.dart'; +/// RU: Класс Module является основой для пользовательских модулей. +/// Этот класс нужен для инициализации [Scope]. +/// +/// RU: The Module class is the basis for custom modules. +/// This class is needed to initialize [Scope]. +/// abstract class Module { final Set _bindingSet = HashSet(); + /// RU: Метод добавляет в коллекцию модуля [Binding] экземпляр. + /// + /// ENG: The method adds an instance to the collection of the [Binding] module. + /// + /// return [Binding] Binding bind() { final binding = Binding(); _bindingSet.add(binding); return binding; } + /// RU: Метод возвращает коллекцию [Binding] экземпляров. + /// + /// ENG: The method returns a collection of [Binding] instances. + /// + /// return [Set] Set get bindingSet => _bindingSet; + /// RU: Абстрактный метод для инициализации пользовательских экземпляров. + /// В этом методе осуществляется конфигурация зависимостей. + /// + /// ENG: Abstract method for initializing custom instances. + /// This method configures dependencies. + /// + /// return [void] void builder(Scope currentScope); } From ec75ad91726ca28250743ed116dae69284554225 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 22 Apr 2021 10:25:38 +0300 Subject: [PATCH 06/14] added documentation --- lib/experimental/scope.dart | 49 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/experimental/scope.dart b/lib/experimental/scope.dart index dbf9dbb..75d8684 100644 --- a/lib/experimental/scope.dart +++ b/lib/experimental/scope.dart @@ -8,6 +8,11 @@ Scope openRootScope() => Scope(null); class Scope { final Scope? _parentScope; + /// RU: Метод возвращает родительский [Scope]. + /// + /// ENG: The method returns the parent [Scope]. + /// + /// return [Scope] Scope? get parentScope => _parentScope; final Map _scopeMap = HashMap(); @@ -16,6 +21,11 @@ class Scope { final Set _modulesList = HashSet(); + /// RU: Метод открывает дочерний (дополнительный) [Scope]. + /// + /// ENG: The method opens child (additional) [Scope]. + /// + /// return [Scope] Scope openSubScope(String name) { final subScope = Scope(this); if (!_scopeMap.containsKey(name)) { @@ -24,28 +34,47 @@ class Scope { return _scopeMap[name]!; } + /// RU: Метод закрывает дочерний (дополнительный) [Scope]. + /// + /// ENG: The method closes child (additional) [Scope]. + /// + /// return [Scope] void closeSubScope(String name) { _scopeMap.remove(name); } + /// RU: Метод инициализирует пользовательские модули в [Scope]. + /// + /// ENG: The method initializes custom modules in [Scope]. + /// + /// return [Scope] Scope installModules(List modules) { _modulesList.addAll(modules); modules.forEach((module) => module.builder(this)); return this; } + /// RU: Метод удаляет пользовательские модули из [Scope]. + /// + /// ENG: This method removes custom modules from [Scope]. + /// + /// return [Scope] Scope dropModules() { _modulesList.removeAll(_modulesList); return this; } - /** - * Возвращает найденную зависимость, определенную параметром типа [T]. - * Выдает [StateError], если зависимость не может быть разрешена. - * Если вы хотите получить [null], если зависимость не может быть найдена, - * то используйте вместо этого [tryResolve] - * @return - возвращает объект типа [T] или [StateError] - */ + /// RU: Возвращает найденную зависимость, определенную параметром типа [T]. + /// Выдает [StateError], если зависимость не может быть разрешена. + /// Если вы хотите получить [null], если зависимость не может быть найдена, + /// то используйте вместо этого [tryResolve] + /// return - возвращает объект типа [T] или [StateError] + /// + /// ENG: Returns the found dependency specified by the type parameter [T]. + /// Throws [StateError] if the dependency cannot be resolved. + /// If you want to get [null] if the dependency cannot be found then use [tryResolve] instead + /// return - returns an object of type [T] or [StateError] + /// T resolve({String? named}) { var resolved = tryResolve(named: named); if (resolved != null) { @@ -56,9 +85,9 @@ class Scope { } } - /** - * Возвращает найденную зависимость типа [T] или null, если она не может быть найдена. - */ + /// RU: Возвращает найденную зависимость типа [T] или null, если она не может быть найдена. + /// ENG: Returns found dependency of type [T] or null if it cannot be found. + /// T? tryResolve({String? named}) { // 1 Поиск зависимости по всем модулям текущего скоупа if (_modulesList.isNotEmpty) { From e102b15022d73d04e265b468cc57171b88e36c9e Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 22 Apr 2021 15:22:09 +0300 Subject: [PATCH 07/14] added documents --- lib/experimental/di.dart | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/experimental/di.dart b/lib/experimental/di.dart index 533dda5..3f8578c 100644 --- a/lib/experimental/di.dart +++ b/lib/experimental/di.dart @@ -1 +1,26 @@ -class DartDi {} +import 'package:dart_di/experimental/scope.dart'; + +Scope? _rootScope = null; + +class DartDi { + /// RU: Метод открывает главный [Scope]. + /// ENG: The method opens the main [Scope]. + /// + /// return + static Scope openRootScope() { + if (_rootScope == null) { + _rootScope = Scope(null); + } + return _rootScope!; + } + + /// RU: Метод закрывает главный [Scope]. + /// ENG: The method close the main [Scope]. + /// + /// + static void closeRootScope() { + if (_rootScope != null) { + _rootScope = null; + } + } +} From 1ddbb74e3f0b2e1ec138e02831aa07e686281b9f Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 22 Apr 2021 17:30:32 +0300 Subject: [PATCH 08/14] added documentation --- README.md | 3 + doc/quick_start_en.md | 0 doc/quick_start_ru.md | 195 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 doc/quick_start_en.md create mode 100644 doc/quick_start_ru.md diff --git a/README.md b/README.md index 7c9e048..6d70669 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Экспериментальная разработка DI на ЯП Dart +- [New Api ENG](/doc/quick_start_en.md) +- [New Api RU](/doc/quick_start_ru.md) + ## Документация ### Быстрый старт diff --git a/doc/quick_start_en.md b/doc/quick_start_en.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/quick_start_ru.md b/doc/quick_start_ru.md new file mode 100644 index 0000000..85da279 --- /dev/null +++ b/doc/quick_start_ru.md @@ -0,0 +1,195 @@ +# Быстрый старт + +## Основные компоненты DI + + +### Binding + +Binding - по сути это конфигуратор для пользовательского instance, который соддержит методы для конфигурирования зависимости. + +Есть два основных метода для инициализации пользовательского instance `toInstance()` и `toProvide()` и вспомогательных `withName()` и `singeltone()`. + +`toInstance()` - принимает готовый экземпляр + +`toProvide()` -  принимает функцию `provider` (конструктор экземпляра) + +`withName()` - принимает строку для именования экземпляра. По этому имени можно будет извлечь instance из DI контейнера + +`singeltone()` - устанавливает флаг в Binding, который говорит DI контейнеру, что зависимость одна. По этому имени можно будет извлечь instance из DI контейнера. Об + +Пример: + +```dart + // инициализация экземпляра текстовой строки через метод toInstance() + Binding().toInstance("hello world"); + + // или + + // инициализация экземпляра текстовой строки + Binding().toProvide(() => "hello world"); + + // инициализация экземпляра строки с именем + Binding().withName("my_string").toInstance("hello world"); + // или + Binding().withName("my_string").toProvide(() => "hello world"); + + // инициализация экземпляра, как сингелтон + Binding().toInstance("hello world"); + // или + Binding().toProvide(() => "hello world").singeltone(); + +``` + +### Module + +Module - это контейнер пользовательских instances, и на основе которого пользователь может создавать свои модули. Пользователь в своем модуле должен реализовать метод `void builder(Scope currentScope)`. + + +Пример: + +```dart +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().toInstance(ApiClientMock()); + } +} +``` + +### Scope + +Scope - это контейнер, который хранит все дерево зависимостей (scope,modules,instances). +Через scope можно получить доступ к `instance`, для этого нужно вызвать метод `resolve()` и указать тип объекта, а так же можно передать дополнительные параметры. + +Пример: + +```dart + // открыть главный scope + final rootScope = DartDi.openRootScope(); + + // инициализация scope пользовательским модулем + rootScope.installModules([AppModule()]); + + // получаем экземпляр класса + final str = rootScope.resolve(); + // или + final str = rootScope.tryResolve(); + + // закрыть главный scope + DartDi.closeRootScope(); +``` + +## Пример приложения + + +```dart +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/experimental/module.dart'; + +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().withName("apiClientMock").toInstance(ApiClientMock()); + bind().withName("apiClientImpl").toInstance(ApiClientImpl()); + } +} + +class FeatureModule extends Module { + bool isMock; + + FeatureModule({required this.isMock}); + + @override + void builder(Scope currentScope) { + bind() + .withName("networkRepo") + .toProvide( + () => NetworkDataRepository( + currentScope.resolve( + named: isMock ? "apiClientMock" : "apiClientImpl", + ), + ), + ) + .singeltone(); + bind().toProvide( + () => DataBloc( + currentScope.resolve(named: "networkRepo"), + ), + ); + } +} + +void main() async { + final scope = openRootScope().installModules([ + AppModule(), + ]); + + final subScope = scope + .openSubScope("featureScope") + .installModules([FeatureModule(isMock: true)]); + + final dataBloc = subScope.resolve(); + dataBloc.data.listen((d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), onDone: () => print('DONE')); + + await dataBloc.fetchData(); +} + +class DataBloc { + final DataRepository _dataRepository; + + Stream get data => _dataController.stream; + StreamController _dataController = new StreamController.broadcast(); + + DataBloc(this._dataRepository); + + Future fetchData() async { + try { + _dataController.sink.add(await _dataRepository.getData()); + } catch (e) { + _dataController.sink.addError(e); + } + } + + void dispose() { + _dataController.close(); + } +} + +abstract class DataRepository { + Future getData(); +} + +class NetworkDataRepository implements DataRepository { + final ApiClient _apiClient; + final _token = 'token'; + + NetworkDataRepository(this._apiClient); + + @override + Future getData() async => await _apiClient.sendRequest( + url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); +} + +abstract class ApiClient { + Future sendRequest({@required String url, String token, Map requestBody}); +} + +class ApiClientMock implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Local Data'; + } +} + +class ApiClientImpl implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Network data'; + } +} +``` \ No newline at end of file From 2568414a1b9b99ee66583c084692c809bbf353f0 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Fri, 23 Apr 2021 08:43:10 +0300 Subject: [PATCH 09/14] implemented doc --- doc/quick_start_en.md | 195 ++++++++++++++++++++++++++++++++++++++++++ doc/quick_start_ru.md | 2 +- 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/doc/quick_start_en.md b/doc/quick_start_en.md index e69de29..72d650e 100644 --- a/doc/quick_start_en.md +++ b/doc/quick_start_en.md @@ -0,0 +1,195 @@ +# Quick start + +## Main components DI + + +### Binding + +Binding is a custom instance configurator that contains methods for configuring a dependency. + +There are two main methods for initializing a custom instance `toInstance ()` and `toProvide ()` and auxiliary `withName ()` and `singeltone ()`. + +`toInstance()` - takes a initialized instance + +`toProvide()` -  takes a `provider` function (instance constructor) + +`withName()` - takes a string to name the instance. By this name, it will be possible to extract instance from the DI container + +`singeltone()` - sets a flag in the Binding that tells the DI container that there is only one dependency. + +Example: + +```dart + // initializing a text string instance through a method toInstance() + Binding().toInstance("hello world"); + + // or + + // initializing a text string instance + Binding().toProvide(() => "hello world"); + + // initializing an instance of a string named + Binding().withName("my_string").toInstance("hello world"); + // or + Binding().withName("my_string").toProvide(() => "hello world"); + + // instance initialization like singleton + Binding().toInstance("hello world"); + // or + Binding().toProvide(() => "hello world").singeltone(); + +``` + +### Module + +Module is a container of user instances, and on the basis of which the user can create their modules. The user in his module must implement the `void builder (Scope currentScope)` method. + + +Example: + +```dart +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().toInstance(ApiClientMock()); + } +} +``` + +### Scope + +Scope is a container that stores the entire dependency tree (scope, modules, instances). +Through the scope, you can access the custom `instance`, for this you need to call the `resolve()` method and specify the type of the object, and you can also pass additional parameters. + +Example: + +```dart + // open main scope + final rootScope = DartDi.openRootScope(); + + // initializing scope with a custom module + rootScope.installModules([AppModule()]); + + // takes custom instance + final str = rootScope.resolve(); + // or + final str = rootScope.tryResolve(); + + // close main scope + DartDi.closeRootScope(); +``` + +## Example app + + +```dart +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/experimental/module.dart'; + +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().withName("apiClientMock").toInstance(ApiClientMock()); + bind().withName("apiClientImpl").toInstance(ApiClientImpl()); + } +} + +class FeatureModule extends Module { + bool isMock; + + FeatureModule({required this.isMock}); + + @override + void builder(Scope currentScope) { + bind() + .withName("networkRepo") + .toProvide( + () => NetworkDataRepository( + currentScope.resolve( + named: isMock ? "apiClientMock" : "apiClientImpl", + ), + ), + ) + .singeltone(); + bind().toProvide( + () => DataBloc( + currentScope.resolve(named: "networkRepo"), + ), + ); + } +} + +void main() async { + final scope = openRootScope().installModules([ + AppModule(), + ]); + + final subScope = scope + .openSubScope("featureScope") + .installModules([FeatureModule(isMock: true)]); + + final dataBloc = subScope.resolve(); + dataBloc.data.listen((d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), onDone: () => print('DONE')); + + await dataBloc.fetchData(); +} + +class DataBloc { + final DataRepository _dataRepository; + + Stream get data => _dataController.stream; + StreamController _dataController = new StreamController.broadcast(); + + DataBloc(this._dataRepository); + + Future fetchData() async { + try { + _dataController.sink.add(await _dataRepository.getData()); + } catch (e) { + _dataController.sink.addError(e); + } + } + + void dispose() { + _dataController.close(); + } +} + +abstract class DataRepository { + Future getData(); +} + +class NetworkDataRepository implements DataRepository { + final ApiClient _apiClient; + final _token = 'token'; + + NetworkDataRepository(this._apiClient); + + @override + Future getData() async => await _apiClient.sendRequest( + url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); +} + +abstract class ApiClient { + Future sendRequest({@required String url, String token, Map requestBody}); +} + +class ApiClientMock implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Local Data'; + } +} + +class ApiClientImpl implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Network data'; + } +} +``` \ No newline at end of file diff --git a/doc/quick_start_ru.md b/doc/quick_start_ru.md index 85da279..841fe5a 100644 --- a/doc/quick_start_ru.md +++ b/doc/quick_start_ru.md @@ -15,7 +15,7 @@ Binding - по сути это конфигуратор для пользов `withName()` - принимает строку для именования экземпляра. По этому имени можно будет извлечь instance из DI контейнера -`singeltone()` - устанавливает флаг в Binding, который говорит DI контейнеру, что зависимость одна. По этому имени можно будет извлечь instance из DI контейнера. Об +`singeltone()` - устанавливает флаг в Binding, который говорит DI контейнеру, что зависимость одна. Пример: From 98f12c5eb79c1f664af8daf5846f031d040d452b Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Fri, 23 Apr 2021 10:34:33 +0300 Subject: [PATCH 10/14] refactored code and implemented unit tests --- lib/experimental/scope.dart | 3 +- test/experimental/scope_test.dart | 91 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 test/experimental/scope_test.dart diff --git a/lib/experimental/scope.dart b/lib/experimental/scope.dart index 75d8684..3cc40e1 100644 --- a/lib/experimental/scope.dart +++ b/lib/experimental/scope.dart @@ -27,9 +27,8 @@ class Scope { /// /// return [Scope] Scope openSubScope(String name) { - final subScope = Scope(this); if (!_scopeMap.containsKey(name)) { - _scopeMap[name] = subScope; + _scopeMap[name] = Scope(this); } return _scopeMap[name]!; } diff --git a/test/experimental/scope_test.dart b/test/experimental/scope_test.dart new file mode 100644 index 0000000..96017cb --- /dev/null +++ b/test/experimental/scope_test.dart @@ -0,0 +1,91 @@ +import 'package:dart_di/experimental/module.dart'; +import 'package:dart_di/experimental/scope.dart'; +import 'package:test/test.dart'; + +void main() { + group("Without parent scope.", () { + test('Parent scope is null.', () { + final scope = new Scope(null); + expect(scope.parentScope, null); + }); + + test('Open sub scope.', () { + final scope = new Scope(null); + final subScope = scope.openSubScope("subScope"); + expect(scope.openSubScope("subScope"), subScope); + }); + + test("Container throws state error if the value can't be resolved", () { + final scope = new Scope(null); + expect(() => scope.resolve(), throwsA(isA())); + }); + + test('Container resolves value after adding a dependency', () { + final expectedValue = "test string"; + final scope = new Scope(null) + .installModules([TestModule(value: expectedValue)]); + expect(scope.resolve(), expectedValue); + }); + }); + + group('With parent scope.', () { + /* + test( + "Container bind() throws state error (if it's parent already has a resolver)", + () { + final parentScope = new Scope(null) + .installModules([TestModule(value: "string one")]); + final scope = new Scope(parentScope); + + expect( + () => scope.installModules([TestModule(value: "string two")]), + throwsA(isA())); + }); +*/ + test("Container resolve() returns a value from parent container.", () { + final expectedValue = 5; + final parentScope = Scope(null); + final scope = Scope(parentScope); + + parentScope.installModules([TestModule(value: expectedValue)]); + + expect(scope.resolve(), expectedValue); + }); + + test("Container resolve() returns a several value from parent container.", + () { + final expectedIntValue = 5; + final expectedStringValue = "Hello world"; + final parentScope = Scope(null).installModules([ + TestModule(value: expectedIntValue), + TestModule(value: expectedStringValue) + ]); + final scope = Scope(parentScope); + + expect(scope.resolve(), expectedIntValue); + expect(scope.resolve(), expectedStringValue); + }); + + test("Container resolve() throws a state error if parent hasn't value too.", + () { + final parentScope = Scope(null); + final scope = Scope(parentScope); + expect(() => scope.resolve(), throwsA(isA())); + }); + }); +} + +class TestModule extends Module { + final T value; + final String? name; + + TestModule({required this.value, this.name}); + @override + void builder(Scope currentScope) { + if (name == null) { + bind().toInstance(value); + } else { + bind().withName(name!).toInstance(value); + } + } +} From bab560a85626fa7b1371516fc8567133350b9cdf Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Fri, 23 Apr 2021 17:29:42 +0300 Subject: [PATCH 11/14] fixed binding and writed unit tests --- lib/experimental/binding.dart | 8 +- test/experimental/module_test.dart | 296 +++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 test/experimental/module_test.dart diff --git a/lib/experimental/binding.dart b/lib/experimental/binding.dart index 3ab9beb..6dc0e2a 100644 --- a/lib/experimental/binding.dart +++ b/lib/experimental/binding.dart @@ -7,8 +7,8 @@ class Binding { late Mode _mode; late Type _key; late String _name; - late T _instance; - late T Function() _provider; + T? _instance = null; + T? Function()? _provider = null; late bool _isSingeltone = false; late bool _isNamed = false; @@ -84,7 +84,7 @@ class Binding { /// return [Binding] Binding singeltone() { if (_mode == Mode.PROVIDER_INSTANCE) { - _instance = _provider.call(); + _instance = _provider?.call(); } _isSingeltone = true; return this; @@ -100,5 +100,5 @@ class Binding { /// ENG: Resolve instance. /// /// return [T] - T? get provider => _provider.call(); + T? get provider => _provider?.call(); } diff --git a/test/experimental/module_test.dart b/test/experimental/module_test.dart new file mode 100644 index 0000000..db57e7b --- /dev/null +++ b/test/experimental/module_test.dart @@ -0,0 +1,296 @@ +import 'package:dart_di/experimental/binding.dart'; +import 'package:test/test.dart'; + +void main() { + group("Check instance.", () { + group("Without name.", () { + test("Binding resolves null", () { + final binding = Binding(); + expect(binding.instance, null); + }); + + test("Binding check mode", () { + final expectedValue = 5; + final binding = Binding().toInstance(expectedValue); + + expect(binding.mode, Mode.INSTANCE); + }); + + test("Binding check singeltone", () { + final expectedValue = 5; + final binding = Binding().toInstance(expectedValue); + + expect(binding.isSingeltone, true); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = Binding().toInstance(expectedValue); + + expect(binding.instance, expectedValue); + }); + + test("Binding resolves value", () { + final expectedValue = 5; + final binding = Binding().toInstance(expectedValue); + expect(binding.instance, expectedValue); + }); + }); + + group("With name.", () { + test("Binding resolves null", () { + final binding = Binding().withName("expectedValue"); + expect(binding.instance, null); + }); + + test("Binding check mode", () { + final expectedValue = 5; + final binding = + Binding().withName("expectedValue").toInstance(expectedValue); + + expect(binding.mode, Mode.INSTANCE); + }); + + test("Binding check key", () { + final expectedValue = 5; + final binding = + Binding().withName("expectedValue").toInstance(expectedValue); + + expect(binding.key, int); + }); + + test("Binding check singeltone", () { + final expectedValue = 5; + final binding = + Binding().withName("expectedValue").toInstance(expectedValue); + + expect(binding.isSingeltone, true); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = + Binding().withName("expectedValue").toInstance(expectedValue); + + expect(binding.instance, expectedValue); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = + Binding().withName("expectedValue").toInstance(expectedValue); + + expect(binding.name, "expectedValue"); + }); + + test("Binding resolves value", () { + final expectedValue = 5; + final binding = + Binding().withName("expectedValue").toInstance(expectedValue); + expect(binding.instance, expectedValue); + }); + }); + }); + + group("Check provide.", () { + group("Without name.", () { + test("Binding resolves null", () { + final binding = Binding(); + expect(binding.provider, null); + }); + + test("Binding check mode", () { + final expectedValue = 5; + final binding = Binding().toProvide(() => expectedValue); + + expect(binding.mode, Mode.PROVIDER_INSTANCE); + }); + + test("Binding check singeltone", () { + final expectedValue = 5; + final binding = Binding().toProvide(() => expectedValue); + + expect(binding.isSingeltone, false); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = Binding().toProvide(() => expectedValue); + + expect(binding.provider, expectedValue); + }); + + test("Binding resolves value", () { + final expectedValue = 5; + final binding = Binding().toProvide(() => expectedValue); + expect(binding.provider, expectedValue); + }); + }); + + group("With name.", () { + test("Binding resolves null", () { + final binding = Binding().withName("expectedValue"); + expect(binding.provider, null); + }); + + test("Binding check mode", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue); + + expect(binding.mode, Mode.PROVIDER_INSTANCE); + }); + + test("Binding check key", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue); + + expect(binding.key, int); + }); + + test("Binding check singeltone", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue); + + expect(binding.isSingeltone, false); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue); + + expect(binding.provider, expectedValue); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue); + + expect(binding.name, "expectedValue"); + }); + + test("Binding resolves value", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue); + expect(binding.provider, expectedValue); + }); + }); + }); + + group("Check singeltone provide.", () { + group("Without name.", () { + test("Binding resolves null", () { + final binding = Binding().singeltone(); + expect(binding.provider, null); + }); + + test("Binding check mode", () { + final expectedValue = 5; + final binding = + Binding().toProvide(() => expectedValue).singeltone(); + + expect(binding.mode, Mode.PROVIDER_INSTANCE); + }); + + test("Binding check singeltone", () { + final expectedValue = 5; + final binding = + Binding().toProvide(() => expectedValue).singeltone(); + + expect(binding.isSingeltone, true); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = + Binding().toProvide(() => expectedValue).singeltone(); + + expect(binding.provider, expectedValue); + }); + + test("Binding resolves value", () { + final expectedValue = 5; + final binding = + Binding().toProvide(() => expectedValue).singeltone(); + expect(binding.provider, expectedValue); + }); + }); + + group("With name.", () { + test("Binding resolves null", () { + final binding = Binding().withName("expectedValue").singeltone(); + expect(binding.provider, null); + }); + + test("Binding check mode", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue) + .singeltone(); + + expect(binding.mode, Mode.PROVIDER_INSTANCE); + }); + + test("Binding check key", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue) + .singeltone(); + + expect(binding.key, int); + }); + + test("Binding check singeltone", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue) + .singeltone(); + + expect(binding.isSingeltone, true); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue) + .singeltone(); + + expect(binding.provider, expectedValue); + }); + + test("Binding check value", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue) + .singeltone(); + + expect(binding.name, "expectedValue"); + }); + + test("Binding resolves value", () { + final expectedValue = 5; + final binding = Binding() + .withName("expectedValue") + .toProvide(() => expectedValue) + .singeltone(); + expect(binding.provider, expectedValue); + }); + }); + }); +} From 35879380d0fcff83d354149c9df4c0457e1376e4 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Fri, 23 Apr 2021 17:52:33 +0300 Subject: [PATCH 12/14] fixed binding and writed unit tests --- test/experimental/{module_test.dart => binding_test.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/experimental/{module_test.dart => binding_test.dart} (100%) diff --git a/test/experimental/module_test.dart b/test/experimental/binding_test.dart similarity index 100% rename from test/experimental/module_test.dart rename to test/experimental/binding_test.dart From de404d4ee100560302ed4c071a1b1d64fa8bc725 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Mon, 26 Apr 2021 09:43:57 +0300 Subject: [PATCH 13/14] refactored di library. --- README.md | 247 +---------- example/bin/main.dart | 60 ++- example/bin/main_experiment.dart | 109 ----- lib/{experimental => }/binding.dart | 0 lib/dart_di.dart | 7 +- lib/{experimental => }/di.dart | 2 +- lib/di_container.dart | 75 ---- lib/{experimental => }/factory.dart | 2 +- lib/{experimental => }/module.dart | 4 +- lib/resolvers/factory_resolver.dart | 15 - lib/resolvers/resolver.dart | 11 - lib/resolvers/resolvers.dart | 7 - lib/resolvers/resolving_context.dart | 172 -------- lib/resolvers/singelton_resolver.dart | 16 - lib/resolvers/value_resolver.dart | 15 - lib/{experimental => }/scope.dart | 4 +- pubspec.yaml | 2 +- test/{experimental => }/binding_test.dart | 2 +- test/di_container_test.dart | 458 -------------------- test/resolvers/factory_resolver_test.dart | 27 -- test/resolvers/singelton_resolver_test.dart | 36 -- test/resolvers/value_resolver_test.dart | 11 - test/{experimental => }/scope_test.dart | 4 +- 23 files changed, 71 insertions(+), 1215 deletions(-) delete mode 100644 example/bin/main_experiment.dart rename lib/{experimental => }/binding.dart (100%) rename lib/{experimental => }/di.dart (91%) delete mode 100644 lib/di_container.dart rename lib/{experimental => }/factory.dart (56%) rename lib/{experimental => }/module.dart (93%) delete mode 100644 lib/resolvers/factory_resolver.dart delete mode 100644 lib/resolvers/resolver.dart delete mode 100644 lib/resolvers/resolvers.dart delete mode 100644 lib/resolvers/resolving_context.dart delete mode 100644 lib/resolvers/singelton_resolver.dart delete mode 100644 lib/resolvers/value_resolver.dart rename lib/{experimental => }/scope.dart (97%) rename test/{experimental => }/binding_test.dart (99%) delete mode 100644 test/di_container_test.dart delete mode 100644 test/resolvers/factory_resolver_test.dart delete mode 100644 test/resolvers/singelton_resolver_test.dart delete mode 100644 test/resolvers/value_resolver_test.dart rename test/{experimental => }/scope_test.dart (96%) diff --git a/README.md b/README.md index 6d70669..608c033 100644 --- a/README.md +++ b/README.md @@ -1,250 +1,13 @@ # dart_di -Экспериментальная разработка DI на ЯП Dart +Experimental development of DI in the Dart language - [New Api ENG](/doc/quick_start_en.md) - [New Api RU](/doc/quick_start_ru.md) -## Документация -### Быстрый старт +### Features -Основным классом для всех операций является `DiContainer`. Вы можете зарегистрировать свои зависимости, -получив `ResolvingContext` через метод `bind()` и используя его различные методы регистрации зависимостей. -Далее вы можете получить зависимости с помощью `resolve()`. - -Пример: - -```dart -final container = DiContainer(); -container.bind().toValue(SomeServiceImpl()); -/* -... - */ - -// Метод `resolve` просто возвращает зарегистрированный ранее экземпляр -final someService = container.resolve(); -``` - -### Ленивая инициализация - -Если вам нужно создать объект в момент резолвинга, вы можете использовать ленивую (другими словами, по запросу) инициализацию объекта -с помощью метода `toFactoryN()`. - -Пример: - -```dart -final container = DiContainer(); -// В методе `toFactory` вы просто определяете, как построить экземпляр через фабричную лямбду -container.bind().toFactory( () => SomeServiceImpl() ); -/* -... - */ -// Метод `resolve()` будет создавать экземпляр через зарегистрированную фабричную лямбду каждый раз, когда вы вызываете его -final someService = container.resolve(); -final anotherSomeService = container.resolve(); -assert(someService != anotherSomeService); -``` - -Но обычно у вас есть много типов с разными зависимостями, которые образуют граф зависимостей. - -Пример: - -```dart -class A {} -class B {} - -class C { - final A a; - final B b; - - C(this.a, this.b); -} -``` - - -Если вам нужно зарегистрировать некоторый тип, зависящий от других типов из контейнера, -вы можете использовать методы `toFactory1` - `toFactory8`, где число в конце, -является количеством запрошенных через аргументы типов зависимостей. -(Обратите внимание, что вам нужно определить все зависимости в аргументах - `toFactory2`). - - -Пример: - -```dart -class SomeService { - final A a; - final B b; - - SomeService(this.a, this.b); -} - -final container = DiContainer(); -container.bind(A::class).toFactory (() => A()); -container.bind(B::class).toFactory (() => B()); - -/// В фабричной лямбде вы определяете, как построить зависимость от других зависимостей -/// (Порядок разрешенных экземпляров соответствует порядку типов аргументов) -container.bind().toFactory2((a, b) => SomeService(a, b)); - -/* -... - */ - -/// Получаем экземпляр `SomeService` через resolve своих зависимостей. -/// В нашем случае - это resolve A и B -/// Внимание!!! То, что он будет создавать новые экземпляры A и B каждый раз, когда вы вызываете `resolve` SomeService -final someService = container.resolve(); -``` - -### Время жизни экземпляров и контроль области видимости - -Если вы хотите создать экземпляр зарегистрированной зависимости только один раз, -и вам нужно получить/разрешить зависимость много раз в контейнере, то вы можете зарегистрировать -свою зависимость с добавлением `asSingeton()`. Например: - -```dart -final container = DiContainer(); -container.bind() - .toFactory(() => A()) - .asSingleton(); - -container - .bind() - .toFactory(() => B()); - .asSingleton(); - -container.bind().toFactory2((a, b) -> SomeService(a, b)); - -// Код выше означает: Контейнер, регистрирует создание A и B только в первый раз, когда оно будет запрошен, -// и регистрирует создание SomeService каждый раз, когда оно будет запрошен. - -final a = container.resolve(); -final b = container.resolve(); -final anotherA = container.resolve(); -final anotherB = container.resolve(); - -assert(a == anotherA && b == anotherB); - -final someService = container.resolve(); -final anotherSomeService = container.resolve(); - -assert(someService != anotherSomeService); -``` - -Если вы хотите сразу создать свой зарегистрированный экземпляр, вы можете вызвать `resolve()`. Например: - - -```dart -final container = DiContainer(); -// Это заставит создать зависимость после регистрации -container.bind () - .toFactory(() => SomeService()) - .asSingleton() - .resolve(); -``` - -Когда вы работаете со сложным приложением, в большинстве случаев вы можете работать со многими модулями с собственными зависимостями. -Эти модули могут быть настроены различными `DiContainer`-ми. И вы можете прикрепить контейнер к другому, как родительский. -В этом случае родительские зависимости будут видны для дочернего контейнера, -и через него вы можете формировать различные области видимости зависимостей. Например: - -```dart -final parentContainer = DiContainer(); -parentContainer.bind().toFactory(() => A()) - -final childContainer = DiContainer(parentContainer); -// Обратите внимание, что родительская зависимость A видна для дочернего контейнера -final a = childContainer.resolve(); - -/* -// Но следующий код потерпит неудачу с ошибкой, потому что родитель не знает о своем потомке. -final parentContainer = DiContainer(); -final childContainer = DiContainer(); -childContainer.bind().toFactory(() => A()); - -// Выдает ошибку -final a = parentContainer.resolve(); - */ -``` - -### Структура библиотеки - -Библиотека состоит из DiContainer и Resolver. -DiContainer - это контейнер со всеми Resolver для разных типов. А `Resolver` - это просто объект, который знает, как разрешить данный тип. -Многие из resolver-ов обернуты другими, поэтому они могут быть составлены для разных вариантов использования. -Resolver - интерфейс, поэтому он имеет много реализаций. Основным является ResolvingContext. -Вы можете думать об этом как об объекте контекста, который имеет вспомогательные методы для создания различных вариантов resolver-ов (`toFactory`,` toValue`, `asSingleton`). -Но все они просто используют метод `toResolver` для определения некоторого корневого resolver в контексте. -Когда вы запрашиваете тип из контейнера с помощью метода `resolve()`, он просто находит контекст для типа и вызывает корневой resolver, который может вызывать другие resolver-ы. - - -Пример (из ```example```): - -```dart -import 'dart:async'; -import 'package:meta/meta.dart'; -import 'package:dart_di/dart_di.dart'; - -void main() async { - final dataModule = new DiContainer() - ..bind().toValue(new ApiClientMock()) - ..bind() - .toFactory1((c) => new NetworkDataRepository(c)) - ..bind().toFactory1((s) => new DataBloc(s)); - - final dataBloc = dataModule.resolve(); - dataBloc.data.listen((d) => print('Received data: $d'), - onError: (e) => print('Error: $e'), onDone: () => print('DONE')); - - await dataBloc.fetchData(); -} - -class DataBloc { - final DataRepository _dataRepository; - - Stream get data => _dataController.stream; - StreamController _dataController = new StreamController.broadcast(); - - DataBloc(this._dataRepository); - - Future fetchData() async { - try { - _dataController.sink.add(await _dataRepository.getData()); - } catch (e) { - _dataController.sink.addError(e); - } - } - - void dispose() { - _dataController.close(); - } -} - -abstract class DataRepository { - Future getData(); -} - -class NetworkDataRepository implements DataRepository { - final ApiClient _apiClient; - final _token = 'token'; - - NetworkDataRepository(this._apiClient); - - @override - Future getData() async => await _apiClient.sendRequest( - url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); -} - -abstract class ApiClient { - Future sendRequest({@required String url, String token, Map requestBody}); -} - -class ApiClientMock implements ApiClient { - @override - Future sendRequest( - {@required String url, String token, Map requestBody}) async { - return 'hello world'; - } -} -``` +- [x] Scope +- [x] Sub scope +- [x] Initialization instance with name diff --git a/example/bin/main.dart b/example/bin/main.dart index 1affdec..f9c113a 100644 --- a/example/bin/main.dart +++ b/example/bin/main.dart @@ -1,15 +1,51 @@ import 'dart:async'; import 'package:meta/meta.dart'; -import 'package:dart_di/dart_di.dart'; +import 'package:dart_di/scope.dart'; +import 'package:dart_di/module.dart'; + +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().withName("apiClientMock").toInstance(ApiClientMock()); + bind().withName("apiClientImpl").toInstance(ApiClientImpl()); + } +} + +class FeatureModule extends Module { + bool isMock; + + FeatureModule({required this.isMock}); + + @override + void builder(Scope currentScope) { + bind() + .withName("networkRepo") + .toProvide( + () => NetworkDataRepository( + currentScope.resolve( + named: isMock ? "apiClientMock" : "apiClientImpl", + ), + ), + ) + .singeltone(); + bind().toProvide( + () => DataBloc( + currentScope.resolve(named: "networkRepo"), + ), + ); + } +} void main() async { - final dataModule = new DiContainer() - ..bind().toValue(new ApiClientMock()) - ..bind() - .toFactory1((c) => new NetworkDataRepository(c)) - ..bind().toFactory1((s) => new DataBloc(s)); + final scope = openRootScope().installModules([ + AppModule(), + ]); - final dataBloc = dataModule.resolve(); + final subScope = scope + .openSubScope("featureScope") + .installModules([FeatureModule(isMock: true)]); + + final dataBloc = subScope.resolve(); dataBloc.data.listen((d) => print('Received data: $d'), onError: (e) => print('Error: $e'), onDone: () => print('DONE')); @@ -60,6 +96,14 @@ class ApiClientMock implements ApiClient { @override Future sendRequest( {@required String? url, String? token, Map? requestBody}) async { - return 'hello world'; + return 'Local Data'; + } +} + +class ApiClientImpl implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Network data'; } } diff --git a/example/bin/main_experiment.dart b/example/bin/main_experiment.dart deleted file mode 100644 index fa2b968..0000000 --- a/example/bin/main_experiment.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:async'; -import 'package:meta/meta.dart'; -import 'package:dart_di/experimental/scope.dart'; -import 'package:dart_di/experimental/module.dart'; - -class AppModule extends Module { - @override - void builder(Scope currentScope) { - bind().withName("apiClientMock").toInstance(ApiClientMock()); - bind().withName("apiClientImpl").toInstance(ApiClientImpl()); - } -} - -class FeatureModule extends Module { - bool isMock; - - FeatureModule({required this.isMock}); - - @override - void builder(Scope currentScope) { - bind() - .withName("networkRepo") - .toProvide( - () => NetworkDataRepository( - currentScope.resolve( - named: isMock ? "apiClientMock" : "apiClientImpl", - ), - ), - ) - .singeltone(); - bind().toProvide( - () => DataBloc( - currentScope.resolve(named: "networkRepo"), - ), - ); - } -} - -void main() async { - final scope = openRootScope().installModules([ - AppModule(), - ]); - - final subScope = scope - .openSubScope("featureScope") - .installModules([FeatureModule(isMock: true)]); - - final dataBloc = subScope.resolve(); - dataBloc.data.listen((d) => print('Received data: $d'), - onError: (e) => print('Error: $e'), onDone: () => print('DONE')); - - await dataBloc.fetchData(); -} - -class DataBloc { - final DataRepository _dataRepository; - - Stream get data => _dataController.stream; - StreamController _dataController = new StreamController.broadcast(); - - DataBloc(this._dataRepository); - - Future fetchData() async { - try { - _dataController.sink.add(await _dataRepository.getData()); - } catch (e) { - _dataController.sink.addError(e); - } - } - - void dispose() { - _dataController.close(); - } -} - -abstract class DataRepository { - Future getData(); -} - -class NetworkDataRepository implements DataRepository { - final ApiClient _apiClient; - final _token = 'token'; - - NetworkDataRepository(this._apiClient); - - @override - Future getData() async => await _apiClient.sendRequest( - url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); -} - -abstract class ApiClient { - Future sendRequest({@required String url, String token, Map requestBody}); -} - -class ApiClientMock implements ApiClient { - @override - Future sendRequest( - {@required String? url, String? token, Map? requestBody}) async { - return 'Local Data'; - } -} - -class ApiClientImpl implements ApiClient { - @override - Future sendRequest( - {@required String? url, String? token, Map? requestBody}) async { - return 'Network data'; - } -} diff --git a/lib/experimental/binding.dart b/lib/binding.dart similarity index 100% rename from lib/experimental/binding.dart rename to lib/binding.dart diff --git a/lib/dart_di.dart b/lib/dart_di.dart index b673688..7cb51ab 100644 --- a/lib/dart_di.dart +++ b/lib/dart_di.dart @@ -1,5 +1,6 @@ library dart_di; -export 'package:dart_di/di_container.dart'; -export 'package:dart_di/resolvers/resolvers.dart'; -export 'package:dart_di/resolvers/resolving_context.dart'; +export 'package:dart_di/scope.dart'; +export 'package:dart_di/module.dart'; +export 'package:dart_di/binding.dart'; +export 'package:dart_di/di.dart'; diff --git a/lib/experimental/di.dart b/lib/di.dart similarity index 91% rename from lib/experimental/di.dart rename to lib/di.dart index 3f8578c..9f028e1 100644 --- a/lib/experimental/di.dart +++ b/lib/di.dart @@ -1,4 +1,4 @@ -import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/scope.dart'; Scope? _rootScope = null; diff --git a/lib/di_container.dart b/lib/di_container.dart deleted file mode 100644 index 25a8842..0000000 --- a/lib/di_container.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:dart_di/resolvers/resolving_context.dart'; - -/** - * Контейнер - это объект, которой хранит все резолверы зависимостей. - */ -class DiContainer { - final DiContainer? _parent; - - final _resolvers = {}; - - DiContainer([this._parent]); - -/** - * Добавляет resolver зависимостей типа [T] в контейнер. - * Обратите внимание, что перезапись значений внутри одного контейнера запрещена. - * @return - возвращает [ResolvingContext] или [StateError] - */ - ResolvingContext bind() { - var context = ResolvingContext(this); - if (hasInTree()) { - throw StateError( - 'Dependency of type `$T` is already exist in containers tree'); - } - - _resolvers[T] = context; - return context; - } - - /** - * Возвращает разрешенную зависимость, определенную параметром типа [T]. - * Выдает [StateError], если зависимость не может быть разрешена. - * Если вы хотите получить [null], если зависимость не может быть разрешена, - * то используйте вместо этого [tryResolve] - * @return - возвращает объект типа [T] или [StateError] - */ - T resolve() { - var resolved = tryResolve(); - if (resolved != null) { - return resolved; - } else { - throw StateError( - 'Can\'t resolve dependency `$T`. Maybe you forget register it?'); - } - } - - /** - * Возвращает разрешенную зависимость типа [T] или null, если она не может быть разрешена. - */ - T? tryResolve() { - var resolver = _resolvers[T]; - if (resolver != null) { - return resolver.resolve(); - } else { - return _parent?.tryResolve(); - } - } - - /** - * Возвращает true, если у этого контейнера есть средство разрешения зависимостей для типа [T]. - * Если вы хотите проверить его для всего дерева контейнеров, используйте вместо него [hasInTree]. - * @return - возвращает булево значение - */ - bool has() { - return _resolvers.containsKey(T); - } - -/** - * Возвращает true, если контейнер или его родители содержат средство разрешения зависимостей для типа [T]. - * Если вы хотите проверить его только для этого контейнера, используйте вместо него [has]. - * @return - возвращает булево значение - */ - bool hasInTree() { - return has() || (_parent != null && _parent!.hasInTree()); - } -} diff --git a/lib/experimental/factory.dart b/lib/factory.dart similarity index 56% rename from lib/experimental/factory.dart rename to lib/factory.dart index 79fedd0..a7f73bd 100644 --- a/lib/experimental/factory.dart +++ b/lib/factory.dart @@ -1,4 +1,4 @@ -import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/scope.dart'; abstract class Factory { T createInstance(Scope scope); diff --git a/lib/experimental/module.dart b/lib/module.dart similarity index 93% rename from lib/experimental/module.dart rename to lib/module.dart index f760594..d25d14e 100644 --- a/lib/experimental/module.dart +++ b/lib/module.dart @@ -1,7 +1,7 @@ import 'dart:collection'; -import 'package:dart_di/experimental/binding.dart'; -import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/binding.dart'; +import 'package:dart_di/scope.dart'; /// RU: Класс Module является основой для пользовательских модулей. /// Этот класс нужен для инициализации [Scope]. diff --git a/lib/resolvers/factory_resolver.dart b/lib/resolvers/factory_resolver.dart deleted file mode 100644 index 6a70a7a..0000000 --- a/lib/resolvers/factory_resolver.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:dart_di/resolvers/resolver.dart'; - -/** - * Разрешает зависимость для фабричной функции - */ -class FactoryResolver extends Resolver { - final T Function() _factory; - - FactoryResolver(this._factory); - - @override - T resolve() { - return _factory(); - } -} diff --git a/lib/resolvers/resolver.dart b/lib/resolvers/resolver.dart deleted file mode 100644 index e99fc65..0000000 --- a/lib/resolvers/resolver.dart +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Resolver - это абстракция, которая определяет, - * как контейнер будет разрешать зависимость - */ -abstract class Resolver { - /** - * Разрешает зависимость типа [T] - * @return - возвращает объект типа [T] - */ - T? resolve(); -} diff --git a/lib/resolvers/resolvers.dart b/lib/resolvers/resolvers.dart deleted file mode 100644 index 6640ead..0000000 --- a/lib/resolvers/resolvers.dart +++ /dev/null @@ -1,7 +0,0 @@ -/// Resolvers определяет, как разрешить зависимость для контейнера -library resolvers; - -export 'resolver.dart'; -export 'factory_resolver.dart'; -export 'value_resolver.dart'; -export 'singelton_resolver.dart'; diff --git a/lib/resolvers/resolving_context.dart b/lib/resolvers/resolving_context.dart deleted file mode 100644 index 65cb738..0000000 --- a/lib/resolvers/resolving_context.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:dart_di/di_container.dart'; -import 'package:dart_di/resolvers/factory_resolver.dart'; -import 'package:dart_di/resolvers/resolver.dart'; -import 'package:dart_di/resolvers/singelton_resolver.dart'; -import 'package:dart_di/resolvers/value_resolver.dart'; - -class ResolvingContext extends Resolver { - /// Корневой резолвер - Resolver get resolver { - return _resolver as Resolver; - } - - DiContainer _container; - - late Resolver _resolver; - - ResolvingContext(this._container); - -/** - * Разрешает зависимость типа [T] - * @return - возвращает объект типа [T] - */ - @override - T resolve() { - _verify(); - return _resolver.resolve(); - } - - /** - * Добавляет резолвер в качестве корневого резолвера - * С помощью этого метода вы можете добавить любой - * пользовательский резолвер - */ - ResolvingContext toResolver(Resolver resolver) { - _resolver = resolver; - return this; - } - - /** - * Создать резолвер значения - */ - ResolvingContext toValue(T value) { - Resolver resolver = ValueResolver(value as TImpl); - return toResolver(resolver); - } - - /** - * Преобразователь в сингелтон - */ - ResolvingContext asSingleton() { - return toResolver(SingletonResolver(resolver)); - } - - /** - * Создать фабричный resolver без каких-либо зависимостей - */ - ResolvingContext toFactory(TImpl Function() factory) { - Resolver resolver = FactoryResolver(factory); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 1 зависимостью от контейнера - */ - ResolvingContext toFactory1(T Function(T1) factory) { - Resolver resolver = - FactoryResolver(() => factory(_container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 2 зависимостями от контейнера - */ - ResolvingContext toFactory2(T Function(T1, T2) factory) { - Resolver resolver = FactoryResolver( - () => factory(_container.resolve(), _container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 3 зависимостями от контейнера - */ - ResolvingContext toFactory3(T Function(T1, T2, T3) factory) { - Resolver resolver = FactoryResolver(() => factory( - _container.resolve(), - _container.resolve(), - _container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 4 зависимостями от контейнера - */ - ResolvingContext toFactory4( - T Function(T1, T2, T3, T4) factory) { - Resolver resolver = FactoryResolver(() => factory( - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 5 зависимостями от контейнера - */ - ResolvingContext toFactory5( - T Function(T1, T2, T3, T4, T5) factory) { - Resolver resolver = FactoryResolver(() => factory( - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 6 зависимостями от контейнера - */ - ResolvingContext toFactory6( - T Function(T1, T2, T3, T4, T5, T6) factory) { - Resolver resolver = FactoryResolver(() => factory( - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 7 зависимостями от контейнера - */ - ResolvingContext toFactory7( - T Function(T1, T2, T3, T4, T5, T6, T7) factory) { - Resolver resolver = FactoryResolver(() => factory( - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve())); - return toResolver(resolver); - } - - /** - * Создать фабричный resolver с 8 зависимостями от контейнера - */ - ResolvingContext toFactory8( - T Function(T1, T2, T3, T4, T5, T6, T7, T8) factory) { - Resolver resolver = FactoryResolver(() => factory( - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve(), - _container.resolve())); - return toResolver(resolver); - } - - void _verify() { - if (_resolver == null) { - throw StateError("Can\'t resolve T without any resolvers. " + - "Please check, may be you didn\'t do anything after bind()"); - } - } -} diff --git a/lib/resolvers/singelton_resolver.dart b/lib/resolvers/singelton_resolver.dart deleted file mode 100644 index 27906ba..0000000 --- a/lib/resolvers/singelton_resolver.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:dart_di/resolvers/resolver.dart'; - -class SingletonResolver extends Resolver { - Resolver _decoratedResolver; - T? _value = null; - - SingletonResolver(this._decoratedResolver); - - @override - T? resolve() { - if (_value == null) { - _value = _decoratedResolver.resolve(); - } - return _value; - } -} diff --git a/lib/resolvers/value_resolver.dart b/lib/resolvers/value_resolver.dart deleted file mode 100644 index 7460312..0000000 --- a/lib/resolvers/value_resolver.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:dart_di/resolvers/resolver.dart'; - -/** - * Разрешает зависимость для значения - */ -class ValueResolver extends Resolver { - T _value; - - ValueResolver(this._value); - - @override - T resolve() { - return _value; - } -} diff --git a/lib/experimental/scope.dart b/lib/scope.dart similarity index 97% rename from lib/experimental/scope.dart rename to lib/scope.dart index 3cc40e1..dd76f5c 100644 --- a/lib/experimental/scope.dart +++ b/lib/scope.dart @@ -1,7 +1,7 @@ import 'dart:collection'; -import 'package:dart_di/experimental/binding.dart'; -import 'package:dart_di/experimental/module.dart'; +import 'package:dart_di/binding.dart'; +import 'package:dart_di/module.dart'; Scope openRootScope() => Scope(null); diff --git a/pubspec.yaml b/pubspec.yaml index ce7dd6a..65d73d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_di description: Experimental Dependency Injection library. -version: 0.0.2 +version: 0.1.0 author: Sergey Penkovsky homepage: locahost diff --git a/test/experimental/binding_test.dart b/test/binding_test.dart similarity index 99% rename from test/experimental/binding_test.dart rename to test/binding_test.dart index db57e7b..e976d7b 100644 --- a/test/experimental/binding_test.dart +++ b/test/binding_test.dart @@ -1,4 +1,4 @@ -import 'package:dart_di/experimental/binding.dart'; +import 'package:dart_di/binding.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/di_container_test.dart b/test/di_container_test.dart deleted file mode 100644 index 5a5a3d8..0000000 --- a/test/di_container_test.dart +++ /dev/null @@ -1,458 +0,0 @@ -import 'package:dart_di/di_container.dart'; -import 'package:dart_di/resolvers/resolver.dart'; -import 'package:test/test.dart'; -import 'package:mockito/mockito.dart'; - -void main() { - group('Without parent', () { - test('Container bind throws state error if it\'s already has resolver', - () { - final container = new DiContainer(); - container.bind().toResolver(_makeResolver(5)); - - expect(() => container.bind().toResolver(_makeResolver(3)), - throwsA(isA())); - }); - - test("Container resolves value after adding a dependency", () { - final expectedValue = 3; - final container = new DiContainer(); - container.bind().toResolver(_makeResolver(expectedValue)); - expect(container.resolve(), expectedValue); - }); - - test("Container throws state error if the value can't be resolved", () { - final container = DiContainer(); - expect(() => container.resolve(), throwsA(isA())); - }); - - test("Container has() returns true if it has resolver", () { - final expectedValue = 5; - final container = new DiContainer(); - container.bind().toResolver(_makeResolver(expectedValue)); - expect(container.has(), true); - }); - - test("Container has() returns false if it hasn't resolver", () { - final container = new DiContainer(); - expect(container.has(), false); - }); - - test("Container hasInTree() returns true if it has resolver", () { - final expectedValue = 5; - final container = DiContainer(); - container.bind().toResolver(_makeResolver(expectedValue)); - expect(container.hasInTree(), true); - }); - - test("Container hasInTree() returns true if it hasn`t resolver", () { - final container = DiContainer(); - expect(container.hasInTree(), false); - }); - }); - - group('With parent', () { - test( - "Container bind() throws state error (if it's parent already has a resolver)", - () { - final parentContainer = new DiContainer(); - final container = new DiContainer(parentContainer); - - parentContainer.bind().toResolver(_makeResolver(5)); - - expect(() => container.bind().toResolver(_makeResolver(3)), - throwsA(isA())); - }); - - test("Container resolve() returns a value from parent container", () { - final expectedValue = 5; - final parentContainer = DiContainer(); - final container = DiContainer(parentContainer); - - parentContainer.bind().toResolver(_makeResolver(expectedValue)); - - expect(container.resolve(), expectedValue); - }); - - test("Container resolve() returns a several value from parent container", - () { - final expectedIntValue = 5; - final expectedStringValue = "Hello world"; - final parentContainer = DiContainer(); - final container = DiContainer(parentContainer); - - parentContainer.bind().toResolver(_makeResolver(expectedIntValue)); - parentContainer - .bind() - .toResolver(_makeResolver(expectedStringValue)); - - expect(container.resolve(), expectedIntValue); - expect(container.resolve(), expectedStringValue); - }); - - test("Container resolve() throws a state error if parent hasn't value too", - () { - final parentContainer = DiContainer(); - final container = DiContainer(parentContainer); - expect(() => container.resolve(), throwsA(isA())); - }); - - test("Container has() returns false if parent has a resolver", () { - final parentContainer = DiContainer(); - final container = DiContainer(parentContainer); - - parentContainer.bind().toResolver(_makeResolver(5)); - - expect(container.has(), false); - }); - - test("Container has() returns false if parent hasn't a resolver", () { - final parentContainer = DiContainer(); - final container = DiContainer(parentContainer); - - expect(container.has(), false); - }); - - test("Container hasInTree() returns true if parent has a resolver", () { - final parentContainer = DiContainer(); - final container = DiContainer(parentContainer); - - parentContainer.bind().toResolver(_makeResolver(5)); - - expect(container.hasInTree(), true); - }); - - test("Test asSingelton", () { - final expectedIntValue = 10; - final containerA = DiContainer(); - final containerB = DiContainer(containerA); - - containerA.bind().toValue(expectedIntValue).asSingleton(); - - expect(containerB.resolve(), expectedIntValue); - }); - - test("Child container can resolve parent container's value", () { - final containerA = DiContainer(); - final a = AA(); - containerA.bind().toValue(a); - - final containerB = DiContainer(containerA); - final containerC = DiContainer(containerB); - expect(containerC.resolve(), a); - }); - }); - - test("Bind to the factory resolves with value", () { - final container = DiContainer(); - final a = AA(); - container.bind().toFactory(() => a); - - expect(container.resolve(), a); - }); - - test("Bind to the factory resolves with value", () { - final container = DiContainer(); - final a = AA(); - container.bind().toValue(a); - container.bind().toFactory1((a) => DependOnA(a)); - - expect(container.resolve().a, a); - }); - - test("Bind to the factory resolves with 2 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toFactory2((a, b) => DependOnAB(a, b)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - }); - - test("Bind to the factory resolves with 3 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - final c = CC(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toValue(c); - container - .bind() - .toFactory3((a, b, c) => DependOnABC(a, b, c)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - expect(container.resolve().c, c); - }); - - test("Bind to the factory resolves with 4 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - final c = CC(); - final d = DD(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toValue(c); - container.bind().toValue(d); - container - .bind() - .toFactory4((a, b, c, d) => DependOnABCD(a, b, c, d)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - expect(container.resolve().c, c); - expect(container.resolve().d, d); - }); - - test("Bind to the factory resolves with 5 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - final c = CC(); - final d = DD(); - final e = EE(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toValue(c); - container.bind().toValue(d); - container.bind().toValue(e); - container.bind().toFactory5( - (a, b, c, d, e) => DependOnABCDE(a, b, c, d, e)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - expect(container.resolve().c, c); - expect(container.resolve().d, d); - expect(container.resolve().e, e); - }); - - test("Bind to the factory resolves with 6 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - final c = CC(); - final d = DD(); - final e = EE(); - final f = FF(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toValue(c); - container.bind().toValue(d); - container.bind().toValue(e); - container.bind().toValue(f); - container.bind().toFactory6( - (a, b, c, d, e, f) => DependOnABCDEF(a, b, c, d, e, f)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - expect(container.resolve().c, c); - expect(container.resolve().d, d); - expect(container.resolve().e, e); - expect(container.resolve().f, f); - }); - - test("Bind to the factory resolves with 7 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - final c = CC(); - final d = DD(); - final e = EE(); - final f = FF(); - final g = GG(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toValue(c); - container.bind().toValue(d); - container.bind().toValue(e); - container.bind().toValue(f); - container.bind().toValue(g); - container.bind().toFactory7( - (a, b, c, d, e, f, g) => DependOnABCDEFG(a, b, c, d, e, f, g)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - expect(container.resolve().c, c); - expect(container.resolve().d, d); - expect(container.resolve().e, e); - expect(container.resolve().f, f); - expect(container.resolve().g, g); - }); - - test("Bind to the factory resolves with 8 value", () { - final container = DiContainer(); - final a = AA(); - final b = BB(); - final c = CC(); - final d = DD(); - final e = EE(); - final f = FF(); - final g = GG(); - final h = HH(); - container.bind().toValue(a); - container.bind().toValue(b); - container.bind().toValue(c); - container.bind().toValue(d); - container.bind().toValue(e); - container.bind().toValue(f); - container.bind().toValue(g); - container.bind().toValue(h); - container.bind().toFactory8( - (a, b, c, d, e, f, g, h) => DependOnABCDEFGH(a, b, c, d, e, f, g, h)); - - expect(container.resolve().a, a); - expect(container.resolve().b, b); - expect(container.resolve().c, c); - expect(container.resolve().d, d); - expect(container.resolve().e, e); - expect(container.resolve().f, f); - expect(container.resolve().g, g); - expect(container.resolve().h, h); - }); -} - -ResolverMock _makeResolver(T expectedValue) { - final resolverMock = new ResolverMock(); - when(resolverMock.resolve()).thenReturn(expectedValue); - return resolverMock; -} - -class ResolverMock extends Mock implements Resolver {} - -abstract class A {} - -class AA implements A {} - -abstract class B {} - -class BB implements B {} - -abstract class C {} - -class CC implements C {} - -abstract class D {} - -class DD implements D {} - -abstract class E {} - -class EE implements E {} - -abstract class F {} - -class FF implements F {} - -abstract class G {} - -class GG implements G {} - -abstract class H {} - -class HH implements H {} - -class DependOnA { - final A a; - - DependOnA(this.a) : assert(a != null); -} - -class DependOnAB { - final A a; - final B b; - - DependOnAB(this.a, this.b) : assert(a != null && b != null); -} - -class DependOnABC { - final A a; - final B b; - final C c; - - DependOnABC(this.a, this.b, this.c) - : assert(a != null && b != null && c != null); -} - -class DependOnABCD { - final A a; - final B b; - final C c; - final D d; - - DependOnABCD(this.a, this.b, this.c, this.d) - : assert(a != null && b != null && c != null && d != null); -} - -class DependOnABCDE { - final A a; - final B b; - final C c; - final D d; - final E e; - - DependOnABCDE(this.a, this.b, this.c, this.d, this.e) - : assert(a != null && b != null && c != null && d != null && e != null); -} - -class DependOnABCDEF { - final A a; - final B b; - final C c; - final D d; - final E e; - final F f; - - DependOnABCDEF(this.a, this.b, this.c, this.d, this.e, this.f) - : assert(a != null && - b != null && - c != null && - d != null && - e != null && - f != null); -} - -class DependOnABCDEFG { - final A a; - final B b; - final C c; - final D d; - final E e; - final F f; - final G g; - - DependOnABCDEFG(this.a, this.b, this.c, this.d, this.e, this.f, this.g) - : assert(a != null && - b != null && - c != null && - d != null && - e != null && - f != null && - g != null); -} - -class DependOnABCDEFGH { - final A a; - final B b; - final C c; - final D d; - final E e; - final F f; - final G g; - final H h; - - DependOnABCDEFGH( - this.a, this.b, this.c, this.d, this.e, this.f, this.g, this.h) - : assert(a != null && - b != null && - c != null && - d != null && - e != null && - f != null && - g != null && - h != null); -} diff --git a/test/resolvers/factory_resolver_test.dart b/test/resolvers/factory_resolver_test.dart deleted file mode 100644 index 4339ecb..0000000 --- a/test/resolvers/factory_resolver_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:dart_di/resolvers/factory_resolver.dart'; -import 'package:test/test.dart'; -import 'package:mockito/mockito.dart' as mockito; - -void main() { - test('Factory resolver resolves with factory', () { - const expected = 3; - final factoryResolver = new FactoryResolver(() => expected); - - expect(factoryResolver.resolve(), expected); - }); - - test('Factory creates value only after resolve() call', () { - final spy = new SpyMock(); - final factoryResolver = new FactoryResolver(() => spy.onFactory()); - - mockito.verifyNever(spy.onFactory()); - factoryResolver.resolve(); - mockito.verify(spy.onFactory()); - }); -} - -abstract class Spy { - void onFactory(); -} - -class SpyMock extends mockito.Mock implements Spy {} diff --git a/test/resolvers/singelton_resolver_test.dart b/test/resolvers/singelton_resolver_test.dart deleted file mode 100644 index 197b890..0000000 --- a/test/resolvers/singelton_resolver_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:dart_di/resolvers/factory_resolver.dart'; -import 'package:dart_di/resolvers/singelton_resolver.dart'; -import 'package:test/test.dart'; -import 'package:mockito/mockito.dart' as mockito; - -void main() { - test( - 'Not singleton resolver resolves different values after multiple resolve() calls', - () { - const callCount = 3; - final spy = new SpyMock(); - final factoryResolver = new FactoryResolver(() => spy..onFactory()); - - for (var i = 0; i < callCount; i++) factoryResolver.resolve(); - - mockito.verify(spy.onFactory()).called(callCount); - }); - - test('Singleton resolver resolves same value after multiple resolve() calls', - () { - const callCount = 3; - final spy = new SpyMock(); - final singletonResolver = - new SingletonResolver(new FactoryResolver(() => spy..onFactory())); - - for (var i = 0; i < callCount; i++) singletonResolver.resolve(); - - mockito.verify(spy.onFactory()).called(1); - }); -} - -abstract class Spy { - void onFactory(); -} - -class SpyMock extends mockito.Mock implements Spy {} diff --git a/test/resolvers/value_resolver_test.dart b/test/resolvers/value_resolver_test.dart deleted file mode 100644 index 1899fa9..0000000 --- a/test/resolvers/value_resolver_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:dart_di/resolvers/value_resolver.dart'; -import 'package:test/test.dart'; - -void main() { - test('Value resolver resolves with selected value', () { - var a = 3; - final valResolver = new ValueResolver(a); - - expect(valResolver.resolve(), a); - }); -} diff --git a/test/experimental/scope_test.dart b/test/scope_test.dart similarity index 96% rename from test/experimental/scope_test.dart rename to test/scope_test.dart index 96017cb..99bb15c 100644 --- a/test/experimental/scope_test.dart +++ b/test/scope_test.dart @@ -1,5 +1,5 @@ -import 'package:dart_di/experimental/module.dart'; -import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/module.dart'; +import 'package:dart_di/scope.dart'; import 'package:test/test.dart'; void main() { From 565fb3e68267a9fc78031c4a74eb9e1c1f62f4f0 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Mon, 26 Apr 2021 10:40:27 +0300 Subject: [PATCH 14/14] added license header for src. Added changelog --- CHANGELOG.md | 6 ++++++ lib/binding.dart | 13 +++++++++++++ lib/dart_di.dart | 13 +++++++++++++ lib/di.dart | 12 ++++++++++++ lib/factory.dart | 13 +++++++++++++ lib/module.dart | 13 +++++++++++++ lib/scope.dart | 13 +++++++++++++ 7 files changed, 83 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cc55327 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ + +--- + +0.1.0 Initial release + +--- \ No newline at end of file diff --git a/lib/binding.dart b/lib/binding.dart index 6dc0e2a..49e4501 100644 --- a/lib/binding.dart +++ b/lib/binding.dart @@ -1,3 +1,16 @@ +/** + * Copyright 2021 Sergey Penkovsky + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + enum Mode { SIMPLE, INSTANCE, PROVIDER_INSTANCE } /// RU: Класс Binding настраивает параметры экземпляра. diff --git a/lib/dart_di.dart b/lib/dart_di.dart index 7cb51ab..e8bf106 100644 --- a/lib/dart_di.dart +++ b/lib/dart_di.dart @@ -1,3 +1,16 @@ +/** + * Copyright 2021 Sergey Penkovsky + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + library dart_di; export 'package:dart_di/scope.dart'; diff --git a/lib/di.dart b/lib/di.dart index 9f028e1..9ceb522 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,3 +1,15 @@ +/** + * Copyright 2021 Sergey Penkovsky + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import 'package:dart_di/scope.dart'; Scope? _rootScope = null; diff --git a/lib/factory.dart b/lib/factory.dart index a7f73bd..cb4470d 100644 --- a/lib/factory.dart +++ b/lib/factory.dart @@ -1,3 +1,16 @@ +/** + * Copyright 2021 Sergey Penkovsky + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import 'package:dart_di/scope.dart'; abstract class Factory { diff --git a/lib/module.dart b/lib/module.dart index d25d14e..5b21344 100644 --- a/lib/module.dart +++ b/lib/module.dart @@ -1,3 +1,16 @@ +/** + * Copyright 2021 Sergey Penkovsky + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import 'dart:collection'; import 'package:dart_di/binding.dart'; diff --git a/lib/scope.dart b/lib/scope.dart index dd76f5c..b52eb31 100644 --- a/lib/scope.dart +++ b/lib/scope.dart @@ -1,3 +1,16 @@ +/** + * Copyright 2021 Sergey Penkovsky + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import 'dart:collection'; import 'package:dart_di/binding.dart';