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/README.md b/README.md index 7c9e048..608c033 100644 --- a/README.md +++ b/README.md @@ -1,247 +1,13 @@ # dart_di -Экспериментальная разработка DI на ЯП Dart +Experimental development of DI in the Dart language -## Документация - -### Быстрый старт - -Основным классом для всех операций является `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); -} -``` +- [New Api ENG](/doc/quick_start_en.md) +- [New Api RU](/doc/quick_start_ru.md) -Если вам нужно зарегистрировать некоторый тип, зависящий от других типов из контейнера, -вы можете использовать методы `toFactory1` - `toFactory8`, где число в конце, -является количеством запрошенных через аргументы типов зависимостей. -(Обратите внимание, что вам нужно определить все зависимости в аргументах - `toFactory2`). +### Features - -Пример: - -```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/doc/quick_start_en.md b/doc/quick_start_en.md new file mode 100644 index 0000000..72d650e --- /dev/null +++ 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 new file mode 100644 index 0000000..841fe5a --- /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 контейнеру, что зависимость одна. + +Пример: + +```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 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/lib/binding.dart b/lib/binding.dart new file mode 100644 index 0000000..49e4501 --- /dev/null +++ b/lib/binding.dart @@ -0,0 +1,117 @@ +/** + * 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 настраивает параметры экземпляра. +/// ENG: The Binding class configures the settings for the instance. +/// +class Binding { + late Mode _mode; + late Type _key; + late String _name; + T? _instance = null; + T? Function()? _provider = null; + late bool _isSingeltone = false; + late bool _isNamed = false; + + Binding() { + _mode = Mode.SIMPLE; + _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; + _isSingeltone = true; + 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(); + } + _isSingeltone = true; + return this; + } + + /// RU: Поиск экземпляра. + /// ENG: Resolve instance. + /// + /// return [T] + T? get instance => _instance; + + /// RU: Поиск экземпляра. + /// ENG: Resolve instance. + /// + /// return [T] + T? get provider => _provider?.call(); +} diff --git a/lib/dart_di.dart b/lib/dart_di.dart index b673688..e8bf106 100644 --- a/lib/dart_di.dart +++ b/lib/dart_di.dart @@ -1,5 +1,19 @@ +/** + * 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/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/di.dart b/lib/di.dart new file mode 100644 index 0000000..9ceb522 --- /dev/null +++ b/lib/di.dart @@ -0,0 +1,38 @@ +/** + * 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; + +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; + } + } +} 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/factory.dart b/lib/factory.dart new file mode 100644 index 0000000..cb4470d --- /dev/null +++ b/lib/factory.dart @@ -0,0 +1,18 @@ +/** + * 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 { + T createInstance(Scope scope); +} diff --git a/lib/module.dart b/lib/module.dart new file mode 100644 index 0000000..5b21344 --- /dev/null +++ b/lib/module.dart @@ -0,0 +1,54 @@ +/** + * 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'; +import 'package:dart_di/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); +} 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/scope.dart b/lib/scope.dart new file mode 100644 index 0000000..b52eb31 --- /dev/null +++ b/lib/scope.dart @@ -0,0 +1,129 @@ +/** + * 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'; +import 'package:dart_di/module.dart'; + +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(); + + Scope(this._parentScope); + + final Set _modulesList = HashSet(); + + /// RU: Метод открывает дочерний (дополнительный) [Scope]. + /// + /// ENG: The method opens child (additional) [Scope]. + /// + /// return [Scope] + Scope openSubScope(String name) { + if (!_scopeMap.containsKey(name)) { + _scopeMap[name] = Scope(this); + } + 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; + } + + /// 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) { + return resolved; + } else { + throw StateError( + 'Can\'t resolve dependency `$T`. Maybe you forget register it?'); + } + } + + /// 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) { + 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(named: named) : 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/binding_test.dart b/test/binding_test.dart new file mode 100644 index 0000000..e976d7b --- /dev/null +++ b/test/binding_test.dart @@ -0,0 +1,296 @@ +import 'package:dart_di/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); + }); + }); + }); +} 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/scope_test.dart b/test/scope_test.dart new file mode 100644 index 0000000..99bb15c --- /dev/null +++ b/test/scope_test.dart @@ -0,0 +1,91 @@ +import 'package:dart_di/module.dart'; +import 'package:dart_di/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); + } + } +}