mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-23 21:13:35 +00:00
Compare commits
16 Commits
1997110d92
...
0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc43167756 | ||
|
|
e726e3bf30 | ||
|
|
9032abe21a | ||
|
|
0708b87753 | ||
|
|
b1973ca418 | ||
|
|
52c7786a49 | ||
|
|
45f81225b0 | ||
|
|
475ca5aedc | ||
|
|
dce2f4f7a9 | ||
|
|
6d7a52f0fa | ||
|
|
b1c4f908fe | ||
|
|
666221199d | ||
|
|
3868bdb0a7 | ||
|
|
b46cfc36a2 | ||
|
|
1aa0ae045e | ||
|
|
12d877333a |
23
.gitlab-ci.yml
Normal file
23
.gitlab-ci.yml
Normal 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
6
CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
---
|
||||
|
||||
0.1.0 Initial release
|
||||
|
||||
---
|
||||
248
README.md
248
README.md
@@ -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
195
doc/quick_start_en.md
Normal 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
195
doc/quick_start_ru.md
Normal 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';
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
117
lib/binding.dart
Normal 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();
|
||||
}
|
||||
@@ -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
38
lib/di.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
18
lib/factory.dart
Normal 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
54
lib/module.dart
Normal 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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Resolver - это абстракция, которая определяет,
|
||||
* как контейнер будет разрешать зависимость
|
||||
*/
|
||||
abstract class Resolver<T> {
|
||||
/**
|
||||
* Разрешает зависимость типа [T]
|
||||
* @return - возвращает объект типа [T]
|
||||
*/
|
||||
T resolve();
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/// Resolvers определяет, как разрешить зависимость для контейнера
|
||||
library resolvers;
|
||||
|
||||
export 'resolver.dart';
|
||||
export 'factory_resolver.dart';
|
||||
export 'value_resolver.dart';
|
||||
export 'singelton_resolver.dart';
|
||||
@@ -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()");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
129
lib/scope.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
12
pubspec.yaml
12
pubspec.yaml
@@ -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
296
test/binding_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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
91
test/scope_test.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user