refactor: unify DIAdapter with generics, ensure type-safety & scalability in benchmark_di

- Refactor DIAdapter to generic abstract class; align interfaces for Cherrypick, GetIt, Riverpod.
- Remove all dynamic/object usage from dependency registration and bench scenarios.
- Universal getUniversalRegistration function now fully type-safe for all DIs.
- Fix teardown and lifecycle for RiverpodAdapter to prevent disposed Container errors.
- Update CLI and benchmark entry points; validated all scenarios and stress modes for each DI adapter.
This commit is contained in:
Sergey Penkovsky
2025-08-07 13:44:39 +03:00
parent 590b876cf4
commit 54446868e4
7 changed files with 193 additions and 192 deletions

View File

@@ -1,13 +1,18 @@
import 'package:cherrypick/cherrypick.dart';
import 'di_adapter.dart';
/// DIAdapter implementation for the CherryPick DI library using registration callbacks.
class CherrypickDIAdapter implements DIAdapter {
/// Универсальный DIAdapter для CherryPick с поддержкой subScope без дублирования логики.
class CherrypickDIAdapter extends DIAdapter<Scope> {
Scope? _scope;
final bool _isSubScope;
CherrypickDIAdapter([Scope? scope, this._isSubScope = false]) {
_scope = scope;
}
@override
void setupDependencies(void Function(dynamic container) registration) {
_scope = CherryPick.openRootScope();
void setupDependencies(void Function(Scope container) registration) {
_scope ??= CherryPick.openRootScope();
registration(_scope!);
}
@@ -21,45 +26,18 @@ class CherrypickDIAdapter implements DIAdapter {
@override
void teardown() {
CherryPick.closeRootScope();
_scope = null;
if (!_isSubScope) {
CherryPick.closeRootScope();
_scope = null;
}
// SubScope teardown не требуется
}
@override
CherrypickDIAdapter openSubScope(String name) {
final sub = _scope!.openSubScope(name);
return _CherrypickSubScopeAdapter(sub);
return CherrypickDIAdapter(_scope!.openSubScope(name), true);
}
@override
Future<void> waitForAsyncReady() async {}
}
/// Internal adapter for a CherryPick sub-scope (callbacks based).
class _CherrypickSubScopeAdapter extends CherrypickDIAdapter {
final Scope _subScope;
_CherrypickSubScopeAdapter(this._subScope);
@override
void setupDependencies(void Function(dynamic container) registration) {
registration(_subScope);
}
@override
T resolve<T extends Object>({String? named}) =>
named == null ? _subScope.resolve<T>() : _subScope.resolve<T>(named: named);
@override
Future<T> resolveAsync<T extends Object>({String? named}) async =>
named == null ? await _subScope.resolveAsync<T>() : await _subScope.resolveAsync<T>(named: named);
@override
void teardown() {
// subScope teardown не требуется
}
@override
CherrypickDIAdapter openSubScope(String name) {
return _CherrypickSubScopeAdapter(_subScope.openSubScope(name));
}
}

View File

@@ -1,24 +1,21 @@
/// Абстракция для DI-адаптера с использованием функций регистрации.
///
/// Позволяет использовать любые DI-контейнеры: и модульные, и безмодульные.
abstract class DIAdapter {
/// Устанавливает зависимости с помощью одной функции регистрации.
///
/// Функция принимает выбранный DI-контейнер, задаваемый реализацией.
void setupDependencies(void Function(dynamic container) registration);
/// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации.
/// Теперь для каждого адаптера задаём строгий generic тип контейнера.
abstract class DIAdapter<TContainer> {
/// Устанавливает зависимости с помощью строго типизированного контейнера.
void setupDependencies(void Function(TContainer container) registration);
/// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется).
T resolve<T extends Object>({String? named});
/// Асинхронно резолвит экземпляр типа [T].
/// Асинхронно резолвит экземпляр типа [T] (если нужно).
Future<T> resolveAsync<T extends Object>({String? named});
/// Уничтожает/отчищает DI-контейнер.
void teardown();
/// Открывает дочерний под-scope (если применимо).
DIAdapter openSubScope(String name);
/// Открывает дочерний scope и возвращает новый адаптер (если поддерживается).
DIAdapter<TContainer> openSubScope(String name);
/// Ожидание готовности DI контейнера (нужно для async DI, например get_it)
Future<void> waitForAsyncReady();
/// Ожидание готовности DI контейнера (если нужно для async DI).
Future<void> waitForAsyncReady() async {}
}

View File

@@ -1,74 +1,59 @@
import 'package:get_it/get_it.dart';
import 'di_adapter.dart';
class GetItAdapter implements DIAdapter {
/// Универсальный DIAdapter для GetIt c поддержкой scopes и строгой типизацией.
class GetItAdapter extends DIAdapter<GetIt> {
late GetIt _getIt;
@override
void setupDependencies(void Function(dynamic container) registration) {
_getIt = GetIt.asNewInstance();
registration(_getIt);
}
@override
T resolve<T extends Object>({String? named}) => _getIt<T>(instanceName: named);
@override
Future<T> resolveAsync<T extends Object>({String? named}) async => _getIt<T>(instanceName: named);
@override
void teardown() => _getIt.reset();
@override
DIAdapter openSubScope(String name) {
// Открываем новый scope и возвращаем адаптер, который в setupDependencies будет использовать init.
return _GetItScopeAdapter(_getIt, name);
}
@override
Future<void> waitForAsyncReady() async {
await _getIt.allReady();
}
}
class _GetItScopeAdapter implements DIAdapter {
final GetIt _getIt;
final String _scopeName;
final String? _scopeName;
final bool _isSubScope;
bool _scopePushed = false;
void Function(dynamic container)? _pendingRegistration;
_GetItScopeAdapter(this._getIt, this._scopeName);
@override
void setupDependencies(void Function(dynamic container) registration) {
_pendingRegistration = registration;
// Создаём scope через pushNewScope с init для правильной регистрации
_getIt.pushNewScope(
scopeName: _scopeName,
init: (getIt) => _pendingRegistration?.call(getIt),
);
_scopePushed = true;
}
@override
T resolve<T extends Object>({String? named}) => _getIt<T>(instanceName: named);
@override
Future<T> resolveAsync<T extends Object>({String? named}) async => _getIt<T>(instanceName: named);
@override
void teardown() {
if (_scopePushed) {
_getIt.popScope();
_scopePushed = false;
/// Основной (root) и subScope-конструкторы.
GetItAdapter({GetIt? instance, String? scopeName, bool isSubScope = false})
: _scopeName = scopeName,
_isSubScope = isSubScope {
if (instance != null) {
_getIt = instance;
}
}
@override
DIAdapter openSubScope(String name) {
return _GetItScopeAdapter(_getIt, name);
void setupDependencies(void Function(GetIt container) registration) {
if (_isSubScope) {
// Создаём scope через pushNewScope с init
_getIt.pushNewScope(
scopeName: _scopeName,
init: (getIt) => registration(getIt),
);
_scopePushed = true;
} else {
_getIt = GetIt.asNewInstance();
registration(_getIt);
}
}
@override
T resolve<T extends Object>({String? named}) =>
_getIt<T>(instanceName: named);
@override
Future<T> resolveAsync<T extends Object>({String? named}) async =>
_getIt<T>(instanceName: named);
@override
void teardown() {
if (_isSubScope && _scopePushed) {
_getIt.popScope();
_scopePushed = false;
} else {
_getIt.reset();
}
}
@override
GetItAdapter openSubScope(String name) =>
GetItAdapter(instance: _getIt, scopeName: name, isSubScope: true);
@override
Future<void> waitForAsyncReady() async {
await _getIt.allReady();

View File

@@ -1,67 +1,71 @@
import 'package:riverpod/riverpod.dart';
import 'di_adapter.dart';
/// RiverpodAdapter реализует DIAdapter для универсального бенчмарка через Riverpod.
class RiverpodAdapter implements DIAdapter {
late ProviderContainer _container;
late final Map<String?, ProviderBase<Object?>> _namedProviders;
/// Унифицированный DIAdapter для Riverpod с поддержкой scopes и строгой типизацией.
class RiverpodAdapter extends DIAdapter<Map<String, ProviderBase<Object?>>> {
ProviderContainer? _container;
final Map<String, ProviderBase<Object?>> _namedProviders;
final ProviderContainer? _parent;
final bool _isSubScope;
// Основной конструктор
RiverpodAdapter() : _parent = null {
_namedProviders = <String?, ProviderBase<Object?>>{};
}
// Внутренний конструктор для дочерних скоупов
RiverpodAdapter._child(this._container, this._namedProviders, this._parent);
RiverpodAdapter({
ProviderContainer? container,
Map<String, ProviderBase<Object?>>? providers,
ProviderContainer? parent,
bool isSubScope = false,
}) : _container = container,
_namedProviders = providers ?? <String, ProviderBase<Object?>>{},
_parent = parent,
_isSubScope = isSubScope;
@override
void setupDependencies(void Function(dynamic container) registration) {
// Для главного контейнера
_container = _parent == null
void setupDependencies(void Function(Map<String, ProviderBase<Object?>> container) registration) {
_container ??= _parent == null
? ProviderContainer()
: ProviderContainer(parent: _parent);
registration(_namedProviders);
}
/// Регистрировать провайдеры нужно по имени-сервису.
/// Пример: container['SomeClass'] = Provider((ref) => SomeClass());
@override
T resolve<T extends Object>({String? named}) {
final provider = _namedProviders[named ?? T.toString()];
final key = named ?? T.toString();
final provider = _namedProviders[key];
if (provider == null) {
throw Exception('Provider not found for $named');
throw Exception('Provider not found for $key');
}
return _container.read(provider) as T;
return _container!.read(provider) as T;
}
@override
Future<T> resolveAsync<T extends Object>({String? named}) async {
final provider = _namedProviders[named ?? T.toString()];
final key = named ?? T.toString();
final provider = _namedProviders[key];
if (provider == null) {
throw Exception('Provider not found for $named');
throw Exception('Provider not found for $key');
}
// Если это FutureProvider — используем .future
if (provider.runtimeType.toString().contains('FutureProvider')) {
final result = await _container.read((provider as dynamic).future);
return result as T;
return await _container!.read((provider as dynamic).future) as T;
}
return resolve<T>(named: named);
}
@override
void teardown() {
_container.dispose();
_container?.dispose();
_container = null;
_namedProviders.clear();
}
@override
DIAdapter openSubScope(String name) {
// Создаём дочерний scope через новый контейнер с parent
final childContainer = ProviderContainer(parent: _container);
// Провайдеры будут унаследованы (immutable копия), но при желании можно их расширять в дочернем scope.
return RiverpodAdapter._child(childContainer, Map.of(_namedProviders), _container);
RiverpodAdapter openSubScope(String name) {
final newContainer = ProviderContainer(parent: _container);
return RiverpodAdapter(
container: newContainer,
providers: Map.of(_namedProviders),
parent: _container,
isSubScope: true,
);
}
@override