Руководства
Объектный граф на чистом Dart
Асинхронные клиенты, Repository, сервисы, дочерние переопределения и завершение по зависимостям.
Объектный граф на чистом Dart
В этом руководстве создаётся граф приложения с асинхронно инициализируемым клиентом, Repository и сервисом. Затем добавляется дочерний Scope предпросмотра с Repository в памяти и проверяется завершение в порядке зависимостей.
Перед началом
dart pub add ark_di:^1.1.0
Используются обычные классы с внедрением через конструкторы:
final class AppConfig {
const AppConfig(this.apiBaseUrl);
final String apiBaseUrl;
}
abstract interface class CatalogRepository {
Future<List<String>> search(String query);
void close();
}
final class CatalogService {
CatalogService(this._repository);
final CatalogRepository _repository;
Future<List<String>> search(String query) => _repository.search(query);
void close() {}
}
Эти классы не импортируют Ark DI. Способ создания конкретных объектов известен только корню композиции.
1. Создайте контейнер приложения
final lifecycle = <String>[];
final application = DiContainer.build((binder) {
binder.bindInstance<AppConfig>(
const AppConfig('https://api.example.com'),
);
binder.bindAsyncLazySingleton<ApiClient>(
(resolver) async {
final config = resolver.get<AppConfig>();
return ApiClient.connect(config.apiBaseUrl);
},
dispose: (client) {
client.close();
lifecycle.add('api-client');
},
);
binder.bindAsyncLazySingleton<CatalogRepository>(
(resolver) async =>
RemoteCatalogRepository(await resolver.getAsync<ApiClient>()),
dispose: (repository) {
repository.close();
lifecycle.add('repository');
},
);
binder.bindAsyncLazySingleton<CatalogService>(
(resolver) async =>
CatalogService(await resolver.getAsync<CatalogRepository>()),
dispose: (service) {
service.close();
lifecycle.add('service');
},
);
});
Регистрация описывает создание, хранение и закрытие объекта. Factory пока не выполняются: каждый Async Lazy Singleton начинает инициализацию при первом разрешении.
2. Разрешите входную зависимость приложения
final service = await application.getAsync<CatalogService>();
final results = await service.search('dart');
Разрешение CatalogService создаёт зависимости по порядку:
AppConfig → ApiClient → CatalogRepository → CatalogService
Параллельные вызовы разделяют одну выполняющуюся инициализацию каждого Async Lazy Singleton. Неудачная попытка не кэшируется навсегда: следующий запрос может повторить её.
3. Добавьте ограниченный Scope предпросмотра
Предпросмотру нужен Repository в памяти без изменения контейнера приложения:
final preview = application.createChild((binder) {
binder.bindInstance<CatalogRepository>(
MemoryCatalogRepository(<String>['Ark DI guide']),
overrideParent: true,
dispose: (repository) {
repository.close();
lifecycle.add('preview-repository');
},
);
binder.bindLazySingleton<CatalogService>(
(resolver) => CatalogService(resolver.get<CatalogRepository>()),
overrideParent: true,
dispose: (service) {
service.close();
lifecycle.add('preview-service');
},
);
});
final previewService = preview.get<CatalogService>();
final previewResults = await previewService.search('guide');
Вместе с Repository переопределяется потребляющий сервис. Сервис родительского Scope разрешает зависимости в родителе и не может захватить объект с более коротким дочерним жизненным циклом.
4. Закройте дочерний Scope до родителя
await preview.close();
await application.close();
print(lifecycle);
Ожидаемый порядок:
[preview-service, preview-repository, service, repository, api-client]
Ark DI фиксирует рёбра зависимостей во время выполнения Factory. При закрытии он ждёт активные разрешения, затем закрывает потребителей раньше зависимостей. Ошибка одного Disposer не прерывает независимую очистку; все ошибки возвращаются в DiCloseException.
Что показывает пример
- Контейнер настраивается один раз и не изменяется во время работы.
- Прикладные объекты используют внедрение через конструкторы.
- Асинхронное разрешение остаётся явным через
getAsync. - Дочерний Scope изменяет ограниченную часть графа без изменения родителя.
- Владение и завершение задаются регистрацией, а не угадываются по именам методов.
Полные реализации ApiClient, Remote Repository и Memory Repository доступны в исходном примере по ссылке выше.