Compare commits

...

16 Commits

Author SHA1 Message Date
Sergey Penkovsky
fc43167756 added license header for src. Added changelog 2021-04-26 10:40:27 +03:00
Sergey Penkovsky
e726e3bf30 refactored di library. 2021-04-26 09:44:04 +03:00
Sergey Penkovsky
9032abe21a fixed binding and writed unit tests 2021-04-23 17:52:33 +03:00
Sergey Penkovsky
0708b87753 fixed binding and writed unit tests 2021-04-23 17:29:42 +03:00
Sergey Penkovsky
b1973ca418 refactored code and implemented unit tests 2021-04-23 10:34:33 +03:00
Sergey Penkovsky
52c7786a49 implemented doc 2021-04-23 08:43:10 +03:00
Sergey Penkovsky
45f81225b0 added documentation 2021-04-22 17:30:32 +03:00
Sergey Penkovsky
475ca5aedc added documents 2021-04-22 15:22:09 +03:00
Sergey Penkovsky
dce2f4f7a9 added documentation 2021-04-22 10:25:38 +03:00
Sergey Penkovsky
6d7a52f0fa added documentation 2021-04-22 10:10:34 +03:00
Sergey Penkovsky
b1c4f908fe added documentation 2021-04-22 09:56:37 +03:00
Sergey Penkovsky
666221199d fixed resolve method 2021-04-21 08:25:55 +03:00
Sergey Penkovsky
3868bdb0a7 fixed example 2021-04-21 08:12:23 +03:00
Sergey Penkovsky
b46cfc36a2 implemented expiremental di with new api 2021-04-21 08:05:38 +03:00
Sergey Penkovsky
1aa0ae045e Добавить .gitlab-ci.yml 2021-04-14 17:19:35 +03:00
Sergey Penkovsky
12d877333a upgraded code for nullsafety 2021-03-27 19:48:03 +03:00
27 changed files with 1248 additions and 1102 deletions

23
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,23 @@
# This file is a template, and might need editing before it works on your project.
# https://hub.docker.com/r/google/dart
image: google/dart:latest
variables:
# Use to learn more:
# pub run test --help
PUB_VARS: "--platform vm --timeout 30s --concurrency=6 --test-randomize-ordering-seed=random --reporter=expanded"
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .pub-cache/global_packages
before_script:
- export PATH="$PATH":"~/.pub-cache/bin"
- pub get --no-precompile
test:
stage: test
script:
- pub run test $PUB_VARS

6
CHANGELOG.md Normal file
View File

@@ -0,0 +1,6 @@
---
0.1.0 Initial release
---

248
README.md
View File

@@ -1,247 +1,13 @@
# dart_di
Экспериментальная разработка DI на ЯП Dart
Experimental development of DI in the Dart language
## Документация
### Быстрый старт
Основным классом для всех операций является `DiContainer`. Вы можете зарегистрировать свои зависимости,
получив `ResolvingContext` через метод `bind<T>()` и используя его различные методы разрешений.
Далее вы можете получить зависимости с помощью `resolve<T>()`.
Пример:
```dart
final container = DiContainer();
container.bind<SomeService>().toValue(SomeServiceImpl());
/*
...
*/
// Метод `resolve` просто возвращает зарегистрированный ранее экземпляр
final someService = container.resolve<SomeService>();
```
### Ленивая инициализация
Если вам нужно создать объект в момент резолвинга, вы можете использовать ленивую (другими словами, по запросу) инициализацию объекта
с помощью метода `toFactoryN()`.
Пример:
```dart
final container = DiContainer();
// В методе `toFactory` вы просто определяете, как построить экземпляр через фабричную лямбду
container.bind<SomeService>().toFactory( () => SomeServiceImpl() );
/*
...
*/
// Метод `resolve()` будет создавать экземпляр через зарегистрированную фабричную лямбду каждый раз, когда вы вызываете его
final someService = container.resolve<SomeService>();
final anotherSomeService = container.resolve<SomeService>();
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<T1>` - `toFactory8<T1 ... T8>`, где число в конце,
является количеством запрошенных через аргументы типов зависимостей.
(Обратите внимание, что вам нужно определить все зависимости в аргументах - `toFactory2<A1, A2>`).
### Features
Пример:
```dart
class SomeService {
final A a;
final B b;
SomeService(this.a, this.b);
}
final container = DiContainer();
container.bind<A>(A::class).toFactory (() => A());
container.bind<B>(B::class).toFactory (() => B());
/// В фабричной лямбде вы определяете, как построить зависимость от других зависимостей
/// (Порядок разрешенных экземпляров соответствует порядку типов аргументов)
container.bind<SomeService>().toFactory2<A, B>((a, b) => SomeService(a, b));
/*
...
*/
/// Получаем экземпляр `SomeService` через resolve своих зависимостей.
/// В нашем случае - это resolve A и B
/// Внимание!!! То, что он будет создавать новые экземпляры A и B каждый раз, когда вы вызываете `resolve` SomeService
final someService = container.resolve<SomeService>();
```
### Время жизни экземпляров и контроль области видимости
Если вы хотите создать экземпляр зарегистрированной зависимости только один раз,
и вам нужно получить/разрешить зависимость много раз в контейнере, то вы можете зарегистрировать
свою зависимость с добавлением `asSingeton()`. Например:
```dart
final container = DiContainer();
container.bind<A>()
.toFactory(() => A())
.asSingleton();
container
.bind<B>()
.toFactory(() => B());
.asSingleton();
container.bind<SomeService>().toFactory2<A, B>((a, b) -> SomeService(a, b));
// Код выше означает: Контейнер, регистрирует создание A и B только в первый раз, когда оно будет запрошен,
// и регистрирует создание SomeService каждый раз, когда оно будет запрошен.
final a = container.resolve<A>();
final b = container.resolve<B>();
final anotherA = container.resolve<A>();
final anotherB = container.resolve<B>();
assert(a == anotherA && b == anotherB);
final someService = container.resolve<SomeService>();
final anotherSomeService = container.resolve<SomeService>();
assert(someService != anotherSomeService);
```
Если вы хотите сразу создать свой зарегистрированный экземпляр, вы можете вызвать `resolve()`. Например:
```dart
final container = DiContainer();
// Это заставит создать зависимость после регистрации
container.bind <SomeService>()
.toFactory(() => SomeService())
.asSingleton()
.resolve();
```
Когда вы работаете со сложным приложением, в большинстве случаев вы можете работать со многими модулями с собственными зависимостями.
Эти модули могут быть настроены различными `DiContainer`-ми. И вы можете прикрепить контейнер к другому, как родительский.
В этом случае родительские зависимости будут видны для дочернего контейнера,
и через него вы можете формировать различные области видимости зависимостей. Например:
```dart
final parentContainer = DiContainer();
parentContainer.bind<A>().toFactory(() => A())
final childContainer = DiContainer(parentContainer);
// Обратите внимание, что родительская зависимость A видна для дочернего контейнера
final a = childContainer.resolve<A>();
/*
// Но следующий код потерпит неудачу с ошибкой, потому что родитель не знает о своем потомке.
final parentContainer = DiContainer();
final childContainer = DiContainer();
childContainer.bind<A>().toFactory(() => A());
// Выдает ошибку
final a = parentContainer.resolve<A>();
*/
```
### Структура библиотеки
Библиотека состоит из DiContainer и Resolver.
DiContainer - это контейнер со всеми Resolver для разных типов. А `Resolver` - это просто объект, который знает, как разрешить данный тип.
Многие из resolver-ов обернуты другими, поэтому они могут быть составлены для разных вариантов использования.
Resolver - интерфейс, поэтому он имеет много реализаций. Основным является ResolvingContext.
Вы можете думать об этом как об объекте контекста, который имеет вспомогательные методы для создания различных вариантов resolver-ов (`toFactory`,` toValue`, `asSingleton`).
Но все они просто используют метод `toResolver` для определения некоторого корневого resolver в контексте.
Когда вы запрашиваете тип из контейнера с помощью метода `resolve<T>()`, он просто находит контекст для типа и вызывает корневой 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<ApiClient>().toValue(new ApiClientMock())
..bind<DataRepository>()
.toFactory1<ApiClient>((c) => new NetworkDataRepository(c))
..bind<DataBloc>().toFactory1<DataRepository>((s) => new DataBloc(s));
final dataBloc = dataModule.resolve<DataBloc>();
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<String> get data => _dataController.stream;
StreamController<String> _dataController = new StreamController.broadcast();
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
}
void dispose() {
_dataController.close();
}
}
abstract class DataRepository {
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> 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

195
doc/quick_start_en.md Normal file
View File

@@ -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<String>().toInstance("hello world");
// or
// initializing a text string instance
Binding<String>().toProvide(() => "hello world");
// initializing an instance of a string named
Binding<String>().withName("my_string").toInstance("hello world");
// or
Binding<String>().withName("my_string").toProvide(() => "hello world");
// instance initialization like singleton
Binding<String>().toInstance("hello world");
// or
Binding<String>().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<ApiClient>().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<T>()` 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<String>();
// or
final str = rootScope.tryResolve<String>();
// 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<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singeltone();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
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<String> get data => _dataController.stream;
StreamController<String> _dataController = new StreamController.broadcast();
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
}
void dispose() {
_dataController.close();
}
}
abstract class DataRepository {
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> 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';
}
}
```

195
doc/quick_start_ru.md Normal file
View File

@@ -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<String>().toInstance("hello world");
// или
// инициализация экземпляра текстовой строки
Binding<String>().toProvide(() => "hello world");
// инициализация экземпляра строки с именем
Binding<String>().withName("my_string").toInstance("hello world");
// или
Binding<String>().withName("my_string").toProvide(() => "hello world");
// инициализация экземпляра, как сингелтон
Binding<String>().toInstance("hello world");
// или
Binding<String>().toProvide(() => "hello world").singeltone();
```
### Module
Module - это контейнер пользовательских instances, и на основе которого пользователь может создавать свои модули. Пользователь в своем модуле должен реализовать метод `void builder(Scope currentScope)`.
Пример:
```dart
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().toInstance(ApiClientMock());
}
}
```
### Scope
Scope - это контейнер, который хранит все дерево зависимостей (scope,modules,instances).
Через scope можно получить доступ к `instance`, для этого нужно вызвать метод `resolve<T>()` и указать тип объекта, а так же можно передать дополнительные параметры.
Пример:
```dart
// открыть главный scope
final rootScope = DartDi.openRootScope();
// инициализация scope пользовательским модулем
rootScope.installModules([AppModule()]);
// получаем экземпляр класса
final str = rootScope.resolve<String>();
// или
final str = rootScope.tryResolve<String>();
// закрыть главный 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<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singeltone();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
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<String> get data => _dataController.stream;
StreamController<String> _dataController = new StreamController.broadcast();
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
}
void dispose() {
_dataController.close();
}
}
abstract class DataRepository {
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> 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';
}
}
```

View File

@@ -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<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singeltone();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
void main() async {
final dataModule = new DiContainer()
..bind<ApiClient>().toValue(new ApiClientMock())
..bind<DataRepository>()
.toFactory1<ApiClient>((c) => new NetworkDataRepository(c))
..bind<DataBloc>().toFactory1<DataRepository>((s) => new DataBloc(s));
final scope = openRootScope().installModules([
AppModule(),
]);
final dataBloc = dataModule.resolve<DataBloc>();
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
@@ -59,7 +95,15 @@ abstract class ApiClient {
class ApiClientMock implements ApiClient {
@override
Future sendRequest(
{@required String url, String token, Map requestBody}) async {
return 'hello world';
{@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';
}
}

View File

@@ -1,10 +1,10 @@
name: example
version: 1.0.0
author: "Sergey Penkovsky <sergey.penkovsky@gmail.com>"
homepage:
homepage: localhost
environment:
sdk: ">=2.2.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
@@ -12,3 +12,4 @@ dependencies:
path: ../
dev_dependencies:
test: ^1.16.8

117
lib/binding.dart Normal file
View File

@@ -0,0 +1,117 @@
/**
* Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
* 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<T> настраивает параметры экземпляра.
/// ENG: The Binding<T> class configures the settings for the instance.
///
class Binding<T> {
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<T> withName(String name) {
_name = name;
_isNamed = true;
return this;
}
/// RU: Инициализация экземляпяра [value].
/// ENG: Initialization instance [value].
///
/// return [Binding]
Binding<T> toInstance(T value) {
_mode = Mode.INSTANCE;
_instance = value;
_isSingeltone = true;
return this;
}
/// RU: Инициализация экземляпяра  через провайдер [value].
/// ENG: Initialization instance via provider [value].
///
/// return [Binding]
Binding<T> toProvide(T Function() value) {
_mode = Mode.PROVIDER_INSTANCE;
_provider = value;
return this;
}
/// RU: Инициализация экземляпяра  как сингелтон [value].
/// ENG: Initialization instance as a singelton [value].
///
/// return [Binding]
Binding<T> 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();
}

View File

@@ -1,5 +1,19 @@
/**
* Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
* 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';

38
lib/di.dart Normal file
View File

@@ -0,0 +1,38 @@
/**
* Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
* 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;
}
}
}

View File

@@ -1,75 +0,0 @@
import 'package:dart_di/resolvers/resolving_context.dart';
/**
* Контейнер - это объект, которой хранит все резолверы зависимостей.
*/
class DiContainer {
final DiContainer _parent;
final _resolvers = <Type, ResolvingContext>{};
DiContainer([this._parent]);
/**
* Добавляет resolver зависимостей типа [T] в контейнер.
* Обратите внимание, что перезапись значений внутри одного контейнера запрещена.
* @return - возвращает [ResolvingContext] или [StateError]
*/
ResolvingContext<T> bind<T>() {
var context = ResolvingContext<T>(this);
if (hasInTree<T>()) {
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<T>() {
var resolved = tryResolve<T>();
if (resolved != null) {
return resolved;
} else {
throw StateError(
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
}
}
/**
* Возвращает разрешенную зависимость типа [T] или null, если она не может быть разрешена.
*/
T tryResolve<T>() {
var resolver = _resolvers[T];
if (resolver != null) {
return resolver.resolve();
} else {
return _parent?.tryResolve<T>();
}
}
/**
* Возвращает true, если у этого контейнера есть средство разрешения зависимостей для типа [T].
* Если вы хотите проверить его для всего дерева контейнеров, используйте вместо него [hasInTree].
* @return - возвращает булево значение
*/
bool has<T>() {
return _resolvers.containsKey(T);
}
/**
* Возвращает true, если контейнер или его родители содержат средство разрешения зависимостей для типа [T].
* Если вы хотите проверить его только для этого контейнера, используйте вместо него [has].
* @return - возвращает булево значение
*/
bool hasInTree<T>() {
return has<T>() || (_parent != null && _parent.hasInTree<T>());
}
}

18
lib/factory.dart Normal file
View File

@@ -0,0 +1,18 @@
/**
* Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
* 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> {
T createInstance(Scope scope);
}

54
lib/module.dart Normal file
View File

@@ -0,0 +1,54 @@
/**
* Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
* 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<Binding> _bindingSet = HashSet();
/// RU: Метод добавляет в коллекцию модуля [Binding] экземпляр.
///
/// ENG: The method adds an instance to the collection of the [Binding] module.
///
/// return [Binding<T>]
Binding<T> bind<T>() {
final binding = Binding<T>();
_bindingSet.add(binding);
return binding;
}
/// RU: Метод возвращает коллекцию [Binding] экземпляров.
///
/// ENG: The method returns a collection of [Binding] instances.
///
/// return [Set<Binding>]
Set<Binding> get bindingSet => _bindingSet;
/// RU: Абстрактный метод для инициализации пользовательских экземпляров.
/// В этом методе осуществляется конфигурация зависимостей.
///
/// ENG: Abstract method for initializing custom instances.
/// This method configures dependencies.
///
/// return [void]
void builder(Scope currentScope);
}

View File

@@ -1,15 +0,0 @@
import 'package:dart_di/resolvers/resolver.dart';
/**
* Разрешает зависимость для фабричной функции
*/
class FactoryResolver<T> extends Resolver<T> {
final T Function() _factory;
FactoryResolver(this._factory);
@override
T resolve() {
return _factory();
}
}

View File

@@ -1,11 +0,0 @@
/**
* Resolver - это абстракция, которая определяет,
* как контейнер будет разрешать зависимость
*/
abstract class Resolver<T> {
/**
* Разрешает зависимость типа [T]
* @return - возвращает объект типа [T]
*/
T resolve();
}

View File

@@ -1,7 +0,0 @@
/// Resolvers определяет, как разрешить зависимость для контейнера
library resolvers;
export 'resolver.dart';
export 'factory_resolver.dart';
export 'value_resolver.dart';
export 'singelton_resolver.dart';

View File

@@ -1,170 +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<T> extends Resolver {
/// Корневой резолвер
Resolver<T> get resolver => _resolver;
DiContainer _container;
Resolver _resolver;
ResolvingContext(this._container);
/**
* Разрешает зависимость типа [T]
* @return - возвращает объект типа [T]
*/
@override
T resolve() {
_verify();
return _resolver?.resolve();
}
/**
* Добавляет резолвер в качестве корневого резолвера
* С помощью этого метода вы можете добавить любой
* пользовательский резолвер
*/
ResolvingContext<T> toResolver<TImpl extends T>(Resolver<TImpl> resolver) {
_resolver = resolver;
return this;
}
/**
* Создать резолвер значения
*/
ResolvingContext<T> toValue<TImpl extends T>(T value) {
Resolver<TImpl> resolver = ValueResolver(value);
return toResolver<TImpl>(resolver);
}
/**
* Преобразователь в сингелтон
*/
ResolvingContext<T> asSingleton() {
return toResolver(SingletonResolver<T>(resolver));
}
/**
* Создать фабричный resolver без каких-либо зависимостей
*/
ResolvingContext<T> toFactory<TImpl extends T>(TImpl Function() factory) {
Resolver<TImpl> resolver = FactoryResolver<TImpl>(factory);
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 1 зависимостью от контейнера
*/
ResolvingContext<T> toFactory1<T1>(T Function(T1) factory) {
Resolver<T> resolver =
FactoryResolver<T>(() => factory(_container.resolve<T1>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 2 зависимостями от контейнера
*/
ResolvingContext<T> toFactory2<T1, T2>(T Function(T1, T2) factory) {
Resolver<T> resolver = FactoryResolver<T>(
() => factory(_container.resolve<T1>(), _container.resolve<T2>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 3 зависимостями от контейнера
*/
ResolvingContext<T> toFactory3<T1, T2, T3>(T Function(T1, T2, T3) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 4 зависимостями от контейнера
*/
ResolvingContext<T> toFactory4<T1, T2, T3, T4>(
T Function(T1, T2, T3, T4) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 5 зависимостями от контейнера
*/
ResolvingContext<T> toFactory5<T1, T2, T3, T4, T5>(
T Function(T1, T2, T3, T4, T5) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 6 зависимостями от контейнера
*/
ResolvingContext<T> toFactory6<T1, T2, T3, T4, T5, T6>(
T Function(T1, T2, T3, T4, T5, T6) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>(),
_container.resolve<T6>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 7 зависимостями от контейнера
*/
ResolvingContext<T> toFactory7<T1, T2, T3, T4, T5, T6, T7>(
T Function(T1, T2, T3, T4, T5, T6, T7) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>(),
_container.resolve<T6>(),
_container.resolve<T7>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 8 зависимостями от контейнера
*/
ResolvingContext<T> toFactory8<T1, T2, T3, T4, T5, T6, T7, T8>(
T Function(T1, T2, T3, T4, T5, T6, T7, T8) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>(),
_container.resolve<T6>(),
_container.resolve<T7>(),
_container.resolve<T8>()));
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()");
}
}
}

View File

@@ -1,16 +0,0 @@
import 'package:dart_di/resolvers/resolver.dart';
class SingletonResolver<T> extends Resolver<T> {
Resolver<T> _decoratedResolver;
T _value = null;
SingletonResolver(this._decoratedResolver);
@override
T resolve() {
if (_value == null) {
_value = _decoratedResolver.resolve();
}
return _value;
}
}

View File

@@ -1,15 +0,0 @@
import 'package:dart_di/resolvers/resolver.dart';
/**
* Разрешает зависимость для значения
*/
class ValueResolver<T> extends Resolver<T> {
T _value;
ValueResolver(this._value);
@override
T resolve() {
return _value;
}
}

129
lib/scope.dart Normal file
View File

@@ -0,0 +1,129 @@
/**
* Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
* 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<String, Scope> _scopeMap = HashMap();
Scope(this._parentScope);
final Set<Module> _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<Module> 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<T>({String? named}) {
var resolved = tryResolve<T>(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<T>({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;
}
}

View File

@@ -1,16 +1,16 @@
name: dart_di
description: Experimental Dependency Injection library.
version: 0.0.1
version: 0.1.0
author: Sergey Penkovsky <sergey.penkovsky@gmail.com>
homepage:
homepage: locahost
environment:
sdk: ">=2.7.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
meta: ^1.1.8
meta: ^1.3.0
dev_dependencies:
test: ^1.14.3
test: ^1.16.8
mockito: ^4.1.1
mockito: ^5.0.3

296
test/binding_test.dart Normal file
View File

@@ -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<int>();
expect(binding.instance, null);
});
test("Binding check mode", () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.mode, Mode.INSTANCE);
});
test("Binding check singeltone", () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.isSingeltone, true);
});
test("Binding check value", () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
test("Binding resolves value", () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
});
group("With name.", () {
test("Binding resolves null", () {
final binding = Binding<int>().withName("expectedValue");
expect(binding.instance, null);
});
test("Binding check mode", () {
final expectedValue = 5;
final binding =
Binding<int>().withName("expectedValue").toInstance(expectedValue);
expect(binding.mode, Mode.INSTANCE);
});
test("Binding check key", () {
final expectedValue = 5;
final binding =
Binding<int>().withName("expectedValue").toInstance(expectedValue);
expect(binding.key, int);
});
test("Binding check singeltone", () {
final expectedValue = 5;
final binding =
Binding<int>().withName("expectedValue").toInstance(expectedValue);
expect(binding.isSingeltone, true);
});
test("Binding check value", () {
final expectedValue = 5;
final binding =
Binding<int>().withName("expectedValue").toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
test("Binding check value", () {
final expectedValue = 5;
final binding =
Binding<int>().withName("expectedValue").toInstance(expectedValue);
expect(binding.name, "expectedValue");
});
test("Binding resolves value", () {
final expectedValue = 5;
final binding =
Binding<int>().withName("expectedValue").toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
});
});
group("Check provide.", () {
group("Without name.", () {
test("Binding resolves null", () {
final binding = Binding<int>();
expect(binding.provider, null);
});
test("Binding check mode", () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test("Binding check singeltone", () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.isSingeltone, false);
});
test("Binding check value", () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
test("Binding resolves value", () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
});
group("With name.", () {
test("Binding resolves null", () {
final binding = Binding<int>().withName("expectedValue");
expect(binding.provider, null);
});
test("Binding check mode", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue);
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test("Binding check key", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue);
expect(binding.key, int);
});
test("Binding check singeltone", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue);
expect(binding.isSingeltone, false);
});
test("Binding check value", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
test("Binding check value", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue);
expect(binding.name, "expectedValue");
});
test("Binding resolves value", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
});
});
group("Check singeltone provide.", () {
group("Without name.", () {
test("Binding resolves null", () {
final binding = Binding<int>().singeltone();
expect(binding.provider, null);
});
test("Binding check mode", () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singeltone();
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test("Binding check singeltone", () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singeltone();
expect(binding.isSingeltone, true);
});
test("Binding check value", () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singeltone();
expect(binding.provider, expectedValue);
});
test("Binding resolves value", () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singeltone();
expect(binding.provider, expectedValue);
});
});
group("With name.", () {
test("Binding resolves null", () {
final binding = Binding<int>().withName("expectedValue").singeltone();
expect(binding.provider, null);
});
test("Binding check mode", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue)
.singeltone();
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test("Binding check key", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue)
.singeltone();
expect(binding.key, int);
});
test("Binding check singeltone", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue)
.singeltone();
expect(binding.isSingeltone, true);
});
test("Binding check value", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue)
.singeltone();
expect(binding.provider, expectedValue);
});
test("Binding check value", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue)
.singeltone();
expect(binding.name, "expectedValue");
});
test("Binding resolves value", () {
final expectedValue = 5;
final binding = Binding<int>()
.withName("expectedValue")
.toProvide(() => expectedValue)
.singeltone();
expect(binding.provider, expectedValue);
});
});
});
}

View File

@@ -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<T> throws state error if it\'s already has resolver',
() {
final container = new DiContainer();
container.bind<int>().toResolver(_makeResolver(5));
expect(() => container.bind<int>().toResolver(_makeResolver(3)),
throwsA(isA<StateError>()));
});
test("Container resolves value after adding a dependency", () {
final expectedValue = 3;
final container = new DiContainer();
container.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.resolve<int>(), expectedValue);
});
test("Container throws state error if the value can't be resolved", () {
final container = DiContainer();
expect(() => container.resolve<int>(), throwsA(isA<StateError>()));
});
test("Container has() returns true if it has resolver", () {
final expectedValue = 5;
final container = new DiContainer();
container.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.has<int>(), true);
});
test("Container has() returns false if it hasn't resolver", () {
final container = new DiContainer();
expect(container.has<int>(), false);
});
test("Container hasInTree() returns true if it has resolver", () {
final expectedValue = 5;
final container = DiContainer();
container.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.hasInTree<int>(), true);
});
test("Container hasInTree() returns true if it hasn`t resolver", () {
final container = DiContainer();
expect(container.hasInTree<int>(), 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<int>().toResolver(_makeResolver(5));
expect(() => container.bind<int>().toResolver(_makeResolver(3)),
throwsA(isA<StateError>()));
});
test("Container resolve() returns a value from parent container", () {
final expectedValue = 5;
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.resolve<int>(), 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<int>().toResolver(_makeResolver(expectedIntValue));
parentContainer
.bind<String>()
.toResolver(_makeResolver(expectedStringValue));
expect(container.resolve<int>(), expectedIntValue);
expect(container.resolve<String>(), expectedStringValue);
});
test("Container resolve() throws a state error if parent hasn't value too",
() {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
expect(() => container.resolve<int>(), throwsA(isA<StateError>()));
});
test("Container has() returns false if parent has a resolver", () {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(5));
expect(container.has<int>(), false);
});
test("Container has() returns false if parent hasn't a resolver", () {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
expect(container.has<int>(), false);
});
test("Container hasInTree() returns true if parent has a resolver", () {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(5));
expect(container.hasInTree<int>(), true);
});
test("Test asSingelton", () {
final expectedIntValue = 10;
final containerA = DiContainer();
final containerB = DiContainer(containerA);
containerA.bind<int>().toValue(expectedIntValue).asSingleton();
expect(containerB.resolve<int>(), expectedIntValue);
});
test("Child container can resolve parent container's value", () {
final containerA = DiContainer();
final a = AA();
containerA.bind<A>().toValue(a);
final containerB = DiContainer(containerA);
final containerC = DiContainer(containerB);
expect(containerC.resolve<A>(), a);
});
});
test("Bind to the factory resolves with value", () {
final container = DiContainer();
final a = AA();
container.bind<A>().toFactory(() => a);
expect(container.resolve<A>(), a);
});
test("Bind to the factory resolves with value", () {
final container = DiContainer();
final a = AA();
container.bind<A>().toValue(a);
container.bind<DependOnA>().toFactory1<A>((a) => DependOnA(a));
expect(container.resolve<DependOnA>().a, a);
});
test("Bind to the factory resolves with 2 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<DependOnAB>().toFactory2<A, B>((a, b) => DependOnAB(a, b));
expect(container.resolve<DependOnAB>().a, a);
expect(container.resolve<DependOnAB>().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<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container
.bind<DependOnABC>()
.toFactory3<A, B, C>((a, b, c) => DependOnABC(a, b, c));
expect(container.resolve<DependOnABC>().a, a);
expect(container.resolve<DependOnABC>().b, b);
expect(container.resolve<DependOnABC>().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<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container
.bind<DependOnABCD>()
.toFactory4<A, B, C, D>((a, b, c, d) => DependOnABCD(a, b, c, d));
expect(container.resolve<DependOnABCD>().a, a);
expect(container.resolve<DependOnABCD>().b, b);
expect(container.resolve<DependOnABCD>().c, c);
expect(container.resolve<DependOnABCD>().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<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<DependOnABCDE>().toFactory5<A, B, C, D, E>(
(a, b, c, d, e) => DependOnABCDE(a, b, c, d, e));
expect(container.resolve<DependOnABCDE>().a, a);
expect(container.resolve<DependOnABCDE>().b, b);
expect(container.resolve<DependOnABCDE>().c, c);
expect(container.resolve<DependOnABCDE>().d, d);
expect(container.resolve<DependOnABCDE>().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<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<F>().toValue(f);
container.bind<DependOnABCDEF>().toFactory6<A, B, C, D, E, F>(
(a, b, c, d, e, f) => DependOnABCDEF(a, b, c, d, e, f));
expect(container.resolve<DependOnABCDEF>().a, a);
expect(container.resolve<DependOnABCDEF>().b, b);
expect(container.resolve<DependOnABCDEF>().c, c);
expect(container.resolve<DependOnABCDEF>().d, d);
expect(container.resolve<DependOnABCDEF>().e, e);
expect(container.resolve<DependOnABCDEF>().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<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<F>().toValue(f);
container.bind<G>().toValue(g);
container.bind<DependOnABCDEFG>().toFactory7<A, B, C, D, E, F, G>(
(a, b, c, d, e, f, g) => DependOnABCDEFG(a, b, c, d, e, f, g));
expect(container.resolve<DependOnABCDEFG>().a, a);
expect(container.resolve<DependOnABCDEFG>().b, b);
expect(container.resolve<DependOnABCDEFG>().c, c);
expect(container.resolve<DependOnABCDEFG>().d, d);
expect(container.resolve<DependOnABCDEFG>().e, e);
expect(container.resolve<DependOnABCDEFG>().f, f);
expect(container.resolve<DependOnABCDEFG>().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<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<F>().toValue(f);
container.bind<G>().toValue(g);
container.bind<H>().toValue(h);
container.bind<DependOnABCDEFGH>().toFactory8<A, B, C, D, E, F, G, H>(
(a, b, c, d, e, f, g, h) => DependOnABCDEFGH(a, b, c, d, e, f, g, h));
expect(container.resolve<DependOnABCDEFGH>().a, a);
expect(container.resolve<DependOnABCDEFGH>().b, b);
expect(container.resolve<DependOnABCDEFGH>().c, c);
expect(container.resolve<DependOnABCDEFGH>().d, d);
expect(container.resolve<DependOnABCDEFGH>().e, e);
expect(container.resolve<DependOnABCDEFGH>().f, f);
expect(container.resolve<DependOnABCDEFGH>().g, g);
expect(container.resolve<DependOnABCDEFGH>().h, h);
});
}
ResolverMock<T> _makeResolver<T>(T expectedValue) {
final resolverMock = new ResolverMock<T>();
when(resolverMock.resolve()).thenReturn(expectedValue);
return resolverMock;
}
class ResolverMock<T> extends Mock implements Resolver<T> {}
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);
}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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);
});
}

91
test/scope_test.dart Normal file
View File

@@ -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<String>(), throwsA(isA<StateError>()));
});
test('Container resolves value after adding a dependency', () {
final expectedValue = "test string";
final scope = new Scope(null)
.installModules([TestModule<String>(value: expectedValue)]);
expect(scope.resolve<String>(), 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<String>(value: "string one")]);
final scope = new Scope(parentScope);
expect(
() => scope.installModules([TestModule<String>(value: "string two")]),
throwsA(isA<StateError>()));
});
*/
test("Container resolve() returns a value from parent container.", () {
final expectedValue = 5;
final parentScope = Scope(null);
final scope = Scope(parentScope);
parentScope.installModules([TestModule<int>(value: expectedValue)]);
expect(scope.resolve<int>(), expectedValue);
});
test("Container resolve() returns a several value from parent container.",
() {
final expectedIntValue = 5;
final expectedStringValue = "Hello world";
final parentScope = Scope(null).installModules([
TestModule<int>(value: expectedIntValue),
TestModule<String>(value: expectedStringValue)
]);
final scope = Scope(parentScope);
expect(scope.resolve<int>(), expectedIntValue);
expect(scope.resolve<String>(), 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<int>(), throwsA(isA<StateError>()));
});
});
}
class TestModule<T> extends Module {
final T value;
final String? name;
TestModule({required this.value, this.name});
@override
void builder(Scope currentScope) {
if (name == null) {
bind<T>().toInstance(value);
} else {
bind<T>().withName(name!).toInstance(value);
}
}
}