diff --git a/README.md b/README.md index 0f216b2..6d916c2 100644 --- a/README.md +++ b/README.md @@ -1 +1,247 @@ -# dart_di \ No newline at end of file +# dart_di + +Экспериментальная разработка DI на ЯП Dart + +## Документация + +### Быстрый старт + +Основным классом для всех операций является `DiContainer`. Вы можете зарегистрировать свои зависимости, +получив `ResolvingContext` через метод `bind()` и используя его различные методы разрешений. +Далее вы можете получить зависимости с помощью `resolve()`. + +Пример: + +```dart +final container = DiContainer(); +container.bind().toValue(SomeServiceImpl()); +/* +... + */ + +// Метод `resolve` просто возвращает зарегистрированный ранее экземпляр +final someService = container.resolve(); +``` + +### Ленивая инициализация + +Если вам нужно создать объект в момент резолвинга, вы можете использовать ленивую (другими словами, по запросу) инициализацию объекта +с помощью метода `toFactoryN()`. + +Пример: + +```dart +final container = DiContainer(); +// В методе `toFactory` вы просто определяете, как построить экземпляр через фабричную лямбду +container.bind().toFactory( () => SomeServiceImpl() ); +/* +... + */ +// Метод `resolve()` будет создавать экземпляр через зарегистрированную фабричную лямбду каждый раз, когда вы вызываете его +final someService = container.resolve(); +final anotherSomeService = container.resolve(); +assert(someService != anotherSomeService); +``` + +Но обычно у вас есть много типов с разными зависимостями, которые образуют граф зависимостей. + +Пример: + +```dart +class A {} +class B {} + +class C { + final A a; + final B b; + + C(this.a, this.b); +} +``` + + +Если вам нужно зарегистрировать некоторый тип, зависящий от других типов из контейнера, +вы можете использовать методы `toFactory1` - `toFactory8`, где число в конце, +является количеством запрошенных через аргументы типов зависимостей. +(Обратите внимание, что вам нужно определить все зависимости в аргументах - `toFactory2`). + + +Пример: + +```dart +class SomeService { + final A a; + final B b; + + SomeService(this.a, this.b); +} + +final container = DiContainer(); +container.bind(A::class).toFactory (() => A()); +container.bind(B::class).toFactory (() => B()); + +/// В фабричной лямбде вы определяете, как построить зависимость от других зависимостей +/// (Порядок разрешенных экземпляров соответствует порядку типов аргументов) +container.bind().toFactory2((a, b) => SomeService(a, b)); + +/* +... + */ + +/// Получаем экземпляр `SomeService` через resolve своих зависимостей. +/// В нашем случае - это resolve A и B +/// Внимание!!! То, что он будет создавать новые экземпляры A и B каждый раз, когда вы вызываете `resolve` SomeService +final someService = container.resolve(); +``` + +### Время жизни экземпляров и контроль области видимости + +Если вы хотите создать экземпляр зарегистрированной зависимости только один раз, +и вам нужно получить/разрешить зависимость много раз в контейнере, то вы можете зарегистрировать +свою зависимость с добавлением `asSingeton()`. Например: + +```dart +final container = DiContainer(); +container.bind() + .toFactory(() => A()) + .asSingleton(); + +container + .bind() + .toFactory(() => B()); + .asSingleton(); + +container.bind().toFactory2((a, b) -> SomeService(a, b)); + +// Код выше означает: Контейнер, регистрирует создание A и B только в первый раз, когда оно будет запрошен, +// и регистрирует создание SomeService каждый раз, когда оно будет запрошен. + +final a = container.resolve(); +final b = container.resolve(); +final anotherA = container.resolve(); +final anotherB = container.resolve(); + +assert(a == anotherA && b == anotherB); + +final someService = container.resolve(); +final anotherSomeService = container.resolve(); + +assert(someService != anotherSomeService); +``` + +Если вы хотите сразу создать свой зарегистрированный экземпляр, вы можете вызвать `resolve()`. Например: + + +```dart +final container = DiContainer(); +// Это заставит создать зависимость после регистрации +container.bind () + .toFactory(() => SomeService()) + .asSingleton() + .resolve(); +``` + +Когда вы работаете со сложным приложением, в большинстве случаев вы можете работать со многими модулями с собственными зависимостями. +Эти модули могут быть настроены различными `DiContainer`-ми. И вы можете прикрепить контейнер к другому, как родительский. +В этом случае родительские зависимости будут видны для дочернего контейнера, +и через него вы можете формировать различные области видимости зависимостей. Например: + +```dart +final parentContainer = DiContainer(); +parentContainer.bind().toFactory(() => A()) + +final childContainer = DiContainer(parentContainer); +// Обратите внимание, что родительская зависимость A видна для дочернего контейнера +final a = childContainer.resolve(); + +/* +// Но следующий код потерпит неудачу с ошибкой, потому что родитель не знает о своем потомке. +final parentContainer = DiContainer(); +final childContainer = DiContainer(); +childContainer.bind().toFactory(() => A()); + +// Выдает ошибку +final a = parentContainer.resolve(); + */ +``` + +### Структура библиотеки + +Библиотека состоит из DiContainer и Resolver. +DiContainer - это контейнер со всеми Resolver для разных типов. А `Resolver` - это просто объект, который знает, как разрешить данный тип. +Многие из resolver-ов обернуты другими, поэтому они могут быть составлены для разных вариантов использования. +Resolver - интерфейс, поэтому он имеет много реализаций. Основным является ResolvingContext. +Вы можете думать об этом как об объекте контекста, который имеет вспомогательные методы для создания различных вариантов resolver-ов (`toFactory`,` toValue`, `asSingleton`). +Но все они просто используют метод `toResolver` для определения некоторого корневого resolver в контексте. +Когда вы запрашиваете тип из контейнера с помощью метода `resolve()`, он просто находит контекст для типа и вызывает корневой resolver, который может вызывать другие resolver-ы. + + +Пример (из ```example```): + +```dart +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:dart_di/dart_di.dart'; + +void main() async { + final dataModule = new DiContainer() + ..bind().toValue(new ApiClientMock()) + ..bind() + .toFactory1((c) => new NetworkDataRepository(c)) + ..bind().toFactory1((s) => new DataBloc(s)); + + final dataBloc = dataModule.resolve(); + dataBloc.data.listen((d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), onDone: () => print('DONE')); + + await dataBloc.fetchData(); +} + +class DataBloc { + final DataRepository _dataRepository; + + Stream get data => _dataController.stream; + StreamController _dataController = new StreamController.broadcast(); + + DataBloc(this._dataRepository); + + Future fetchData() async { + try { + _dataController.sink.add(await _dataRepository.getData()); + } catch (e) { + _dataController.sink.addError(e); + } + } + + void dispose() { + _dataController.close(); + } +} + +abstract class DataRepository { + Future getData(); +} + +class NetworkDataRepository implements DataRepository { + final ApiClient _apiClient; + final _token = 'token'; + + NetworkDataRepository(this._apiClient); + + @override + Future getData() async => await _apiClient.sendRequest( + url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); +} + +abstract class ApiClient { + Future sendRequest({@required String url, String token, Map requestBody}); +} + +class ApiClientMock implements ApiClient { + @override + Future sendRequest( + {@required String url, String token, Map requestBody}) async { + return 'hello world'; + } +} +``` \ No newline at end of file