mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 13:47:24 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aa0ae045e | ||
|
|
12d877333a |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
# Changelog
|
|
||||||
|
|
||||||
0.1.2 Fixed warnings in code
|
|
||||||
|
|
||||||
---
|
|
||||||
0.1.1+2 Updated libraries and fixed warnings
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
0.1.1+1 Updated pubspec and readme.md
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
0.1.1 Updated pubspec
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
0.1.0 Initial release
|
|
||||||
|
|
||||||
---
|
|
||||||
250
README.md
250
README.md
@@ -1,13 +1,247 @@
|
|||||||
# cherrypick
|
# dart_di
|
||||||
|
|
||||||
Experimental development of DI in the Dart language
|
Экспериментальная разработка DI на ЯП Dart
|
||||||
|
|
||||||
- [New Api ENG](/doc/quick_start_en.md)
|
## Документация
|
||||||
- [New Api RU](/doc/quick_start_ru.md)
|
|
||||||
|
### Быстрый старт
|
||||||
|
|
||||||
|
Основным классом для всех операций является `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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Features
|
Если вам нужно зарегистрировать некоторый тип, зависящий от других типов из контейнера,
|
||||||
|
вы можете использовать методы `toFactory1<T1>` - `toFactory8<T1 ... T8>`, где число в конце,
|
||||||
|
является количеством запрошенных через аргументы типов зависимостей.
|
||||||
|
(Обратите внимание, что вам нужно определить все зависимости в аргументах - `toFactory2<A1, A2>`).
|
||||||
|
|
||||||
- [x] Scope
|
|
||||||
- [x] Sub scope
|
Пример:
|
||||||
- [x] Initialization instance with name
|
|
||||||
|
```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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1 +0,0 @@
|
|||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
# 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 = Cherrypick.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
|
|
||||||
Cherrypick.closeRootScope();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example app
|
|
||||||
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'dart:async';
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:cherrypick/scope.dart';
|
|
||||||
import 'package:cherrypick/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,195 +0,0 @@
|
|||||||
# Быстрый старт
|
|
||||||
|
|
||||||
## Основные компоненты 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 = CherryPick.openRootScope();
|
|
||||||
|
|
||||||
// инициализация scope пользовательским модулем
|
|
||||||
rootScope.installModules([AppModule()]);
|
|
||||||
|
|
||||||
// получаем экземпляр класса
|
|
||||||
final str = rootScope.resolve<String>();
|
|
||||||
// или
|
|
||||||
final str = rootScope.tryResolve<String>();
|
|
||||||
|
|
||||||
// закрыть главный scope
|
|
||||||
Cherrypick.closeRootScope();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Пример приложения
|
|
||||||
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'dart:async';
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:cherrypick/scope.dart';
|
|
||||||
import 'package:cherrypick/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,133 +0,0 @@
|
|||||||
# Example
|
|
||||||
|
|
||||||
pubspec.yaml:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: example
|
|
||||||
version: 1.0.0
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
cherrypick:
|
|
||||||
path: ../
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
test: ^1.16.8
|
|
||||||
```
|
|
||||||
|
|
||||||
main.dart:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'dart:async';
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:cherrypick/scope.dart';
|
|
||||||
import 'package:cherrypick/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,51 +1,15 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:cherrypick/scope.dart';
|
import 'package:dart_di/dart_di.dart';
|
||||||
import 'package:cherrypick/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 {
|
void main() async {
|
||||||
final scope = openRootScope().installModules([
|
final dataModule = new DiContainer()
|
||||||
AppModule(),
|
..bind<ApiClient>().toValue(new ApiClientMock())
|
||||||
]);
|
..bind<DataRepository>()
|
||||||
|
.toFactory1<ApiClient>((c) => new NetworkDataRepository(c))
|
||||||
|
..bind<DataBloc>().toFactory1<DataRepository>((s) => new DataBloc(s));
|
||||||
|
|
||||||
final subScope = scope
|
final dataBloc = dataModule.resolve<DataBloc>();
|
||||||
.openSubScope('featureScope')
|
|
||||||
.installModules([FeatureModule(isMock: true)]);
|
|
||||||
|
|
||||||
final dataBloc = subScope.resolve<DataBloc>();
|
|
||||||
dataBloc.data.listen((d) => print('Received data: $d'),
|
dataBloc.data.listen((d) => print('Received data: $d'),
|
||||||
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
|
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
|
||||||
|
|
||||||
@@ -56,7 +20,7 @@ class DataBloc {
|
|||||||
final DataRepository _dataRepository;
|
final DataRepository _dataRepository;
|
||||||
|
|
||||||
Stream<String> get data => _dataController.stream;
|
Stream<String> get data => _dataController.stream;
|
||||||
final StreamController<String> _dataController = StreamController.broadcast();
|
StreamController<String> _dataController = new StreamController.broadcast();
|
||||||
|
|
||||||
DataBloc(this._dataRepository);
|
DataBloc(this._dataRepository);
|
||||||
|
|
||||||
@@ -96,14 +60,6 @@ class ApiClientMock implements ApiClient {
|
|||||||
@override
|
@override
|
||||||
Future sendRequest(
|
Future sendRequest(
|
||||||
{@required String? url, String? token, Map? requestBody}) async {
|
{@required String? url, String? token, Map? requestBody}) async {
|
||||||
return 'Local Data';
|
return 'hello world';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApiClientImpl implements ApiClient {
|
|
||||||
@override
|
|
||||||
Future sendRequest(
|
|
||||||
{@required String? url, String? token, Map? requestBody}) async {
|
|
||||||
return 'Network data';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ name: example
|
|||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
author: "Sergey Penkovsky <sergey.penkovsky@gmail.com>"
|
author: "Sergey Penkovsky <sergey.penkovsky@gmail.com>"
|
||||||
homepage: localhost
|
homepage: localhost
|
||||||
publish_to: none
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick:
|
dart_di:
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
117
lib/binding.dart
117
lib/binding.dart
@@ -1,117 +0,0 @@
|
|||||||
///
|
|
||||||
/// 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;
|
|
||||||
T? Function()? _provider;
|
|
||||||
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,19 +1,5 @@
|
|||||||
///
|
|
||||||
/// 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;
|
library dart_di;
|
||||||
|
|
||||||
export 'package:cherrypick/scope.dart';
|
export 'package:dart_di/di_container.dart';
|
||||||
export 'package:cherrypick/module.dart';
|
export 'package:dart_di/resolvers/resolvers.dart';
|
||||||
export 'package:cherrypick/binding.dart';
|
export 'package:dart_di/resolvers/resolving_context.dart';
|
||||||
export 'package:cherrypick/di.dart';
|
|
||||||
|
|||||||
37
lib/di.dart
37
lib/di.dart
@@ -1,37 +0,0 @@
|
|||||||
///
|
|
||||||
/// 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:cherrypick/scope.dart';
|
|
||||||
|
|
||||||
Scope? _rootScope;
|
|
||||||
|
|
||||||
class CherryPick {
|
|
||||||
/// RU: Метод открывает главный [Scope].
|
|
||||||
/// ENG: The method opens the main [Scope].
|
|
||||||
///
|
|
||||||
/// return
|
|
||||||
static Scope openRootScope() {
|
|
||||||
_rootScope ??= Scope(null);
|
|
||||||
return _rootScope!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RU: Метод закрывает главный [Scope].
|
|
||||||
/// ENG: The method close the main [Scope].
|
|
||||||
///
|
|
||||||
///
|
|
||||||
static void closeRootScope() {
|
|
||||||
if (_rootScope != null) {
|
|
||||||
_rootScope = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
75
lib/di_container.dart
Normal file
75
lib/di_container.dart
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
///
|
|
||||||
/// 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:cherrypick/scope.dart';
|
|
||||||
|
|
||||||
abstract class Factory<T> {
|
|
||||||
T createInstance(Scope scope);
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
///
|
|
||||||
/// 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:cherrypick/binding.dart';
|
|
||||||
import 'package:cherrypick/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);
|
|
||||||
}
|
|
||||||
15
lib/resolvers/factory_resolver.dart
Normal file
15
lib/resolvers/factory_resolver.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
lib/resolvers/resolver.dart
Normal file
11
lib/resolvers/resolver.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Resolver - это абстракция, которая определяет,
|
||||||
|
* как контейнер будет разрешать зависимость
|
||||||
|
*/
|
||||||
|
abstract class Resolver<T> {
|
||||||
|
/**
|
||||||
|
* Разрешает зависимость типа [T]
|
||||||
|
* @return - возвращает объект типа [T]
|
||||||
|
*/
|
||||||
|
T? resolve();
|
||||||
|
}
|
||||||
7
lib/resolvers/resolvers.dart
Normal file
7
lib/resolvers/resolvers.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/// Resolvers определяет, как разрешить зависимость для контейнера
|
||||||
|
library resolvers;
|
||||||
|
|
||||||
|
export 'resolver.dart';
|
||||||
|
export 'factory_resolver.dart';
|
||||||
|
export 'value_resolver.dart';
|
||||||
|
export 'singelton_resolver.dart';
|
||||||
172
lib/resolvers/resolving_context.dart
Normal file
172
lib/resolvers/resolving_context.dart
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
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 {
|
||||||
|
return _resolver as Resolver<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiContainer _container;
|
||||||
|
|
||||||
|
late 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 as TImpl);
|
||||||
|
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()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
lib/resolvers/singelton_resolver.dart
Normal file
16
lib/resolvers/singelton_resolver.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/resolvers/value_resolver.dart
Normal file
15
lib/resolvers/value_resolver.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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
129
lib/scope.dart
@@ -1,129 +0,0 @@
|
|||||||
///
|
|
||||||
/// 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:cherrypick/binding.dart';
|
|
||||||
import 'package:cherrypick/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 (var module in _modulesList) {
|
|
||||||
for (var 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
pubspec.yaml
18
pubspec.yaml
@@ -1,10 +1,8 @@
|
|||||||
name: cherrypick
|
name: dart_di
|
||||||
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
description: Experimental Dependency Injection library.
|
||||||
version: 0.1.2
|
version: 0.0.2
|
||||||
homepage: https://github.com/pese-git/cherrypick
|
author: Sergey Penkovsky <sergey.penkovsky@gmail.com>
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
homepage: locahost
|
||||||
repository: https://github.com/pese-git/cherrypick
|
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
@@ -13,8 +11,6 @@ dependencies:
|
|||||||
meta: ^1.3.0
|
meta: ^1.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
pedantic: ^1.11.0
|
test: ^1.16.8
|
||||||
|
|
||||||
test: ^1.17.2
|
mockito: ^5.0.3
|
||||||
|
|
||||||
mockito: ^5.0.6
|
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
import 'package:cherrypick/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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
458
test/di_container_test.dart
Normal file
458
test/di_container_test.dart
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
27
test/resolvers/factory_resolver_test.dart
Normal file
27
test/resolvers/factory_resolver_test.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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 {}
|
||||||
36
test/resolvers/singelton_resolver_test.dart
Normal file
36
test/resolvers/singelton_resolver_test.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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 {}
|
||||||
11
test/resolvers/value_resolver_test.dart
Normal file
11
test/resolvers/value_resolver_test.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import 'package:cherrypick/module.dart';
|
|
||||||
import 'package:cherrypick/scope.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('Without parent scope.', () {
|
|
||||||
test('Parent scope is null.', () {
|
|
||||||
final scope = Scope(null);
|
|
||||||
expect(scope.parentScope, null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Open sub scope.', () {
|
|
||||||
final scope = 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 = Scope(null);
|
|
||||||
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Container resolves value after adding a dependency', () {
|
|
||||||
final expectedValue = 'test string';
|
|
||||||
final scope = 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