mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 05:25:19 +00:00
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:
@@ -30,29 +30,69 @@ class BenchmarkCliRunner {
|
||||
for (final c in config.chainCounts) {
|
||||
for (final d in config.nestDepths) {
|
||||
BenchmarkResult benchResult;
|
||||
final di = config.di == 'getit'
|
||||
? GetItAdapter()
|
||||
: config.di == 'riverpod'
|
||||
? RiverpodAdapter()
|
||||
: CherrypickDIAdapter();
|
||||
if (scenario == UniversalScenario.asyncChain) {
|
||||
final benchAsync = UniversalChainAsyncBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runAsync(
|
||||
benchmark: benchAsync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
if (config.di == 'getit') {
|
||||
final di = GetItAdapter();
|
||||
if (scenario == UniversalScenario.asyncChain) {
|
||||
final benchAsync = UniversalChainAsyncBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runAsync(
|
||||
benchmark: benchAsync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
} else {
|
||||
final benchSync = UniversalChainBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runSync(
|
||||
benchmark: benchSync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
}
|
||||
} else if (config.di == 'riverpod') {
|
||||
final di = RiverpodAdapter();
|
||||
if (scenario == UniversalScenario.asyncChain) {
|
||||
final benchAsync = UniversalChainAsyncBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runAsync(
|
||||
benchmark: benchAsync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
} else {
|
||||
final benchSync = UniversalChainBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runSync(
|
||||
benchmark: benchSync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final benchSync = UniversalChainBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runSync(
|
||||
benchmark: benchSync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
final di = CherrypickDIAdapter();
|
||||
if (scenario == UniversalScenario.asyncChain) {
|
||||
final benchAsync = UniversalChainAsyncBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runAsync(
|
||||
benchmark: benchAsync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
} else {
|
||||
final benchSync = UniversalChainBenchmark(di,
|
||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
||||
);
|
||||
benchResult = await BenchmarkRunner.runSync(
|
||||
benchmark: benchSync,
|
||||
warmups: config.warmups,
|
||||
repeats: config.repeats,
|
||||
);
|
||||
}
|
||||
}
|
||||
final timings = benchResult.timings;
|
||||
timings.sort();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,10 +6,12 @@ import '../di_adapters/get_it_adapter.dart';
|
||||
import 'universal_chain_module.dart';
|
||||
import 'package:riverpod/riverpod.dart' as rp;
|
||||
|
||||
/// Возвращает универсальную функцию регистрации зависимостей,
|
||||
/// подходящую под выбранный DI-адаптер.
|
||||
void Function(dynamic) getUniversalRegistration(
|
||||
DIAdapter adapter, {
|
||||
/// Унифицированный generic-колбэк для регистрации зависимостей,
|
||||
/// подходящий под выбранный DI-адаптер.
|
||||
typedef Registration<TContainer> = void Function(TContainer);
|
||||
|
||||
Registration<TContainer> getUniversalRegistration<TContainer>(
|
||||
DIAdapter<TContainer> adapter, {
|
||||
required int chainCount,
|
||||
required int nestingDepth,
|
||||
required UniversalBindingMode bindingMode,
|
||||
@@ -25,8 +27,9 @@ void Function(dynamic) getUniversalRegistration(
|
||||
scenario: scenario,
|
||||
),
|
||||
]);
|
||||
};
|
||||
} else if (adapter is GetItAdapter || adapter.runtimeType.toString().contains('GetItScopeAdapter')) {
|
||||
} as Registration<TContainer>;
|
||||
}
|
||||
if (adapter is GetItAdapter) {
|
||||
return (getIt) {
|
||||
switch (scenario) {
|
||||
case UniversalScenario.asyncChain:
|
||||
@@ -39,7 +42,7 @@ void Function(dynamic) getUniversalRegistration(
|
||||
final prev = level > 1
|
||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
||||
: null;
|
||||
return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?);
|
||||
return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?);
|
||||
},
|
||||
instanceName: depName,
|
||||
);
|
||||
@@ -64,8 +67,8 @@ void Function(dynamic) getUniversalRegistration(
|
||||
UniversalServiceImpl(
|
||||
value: depName,
|
||||
dependency: level > 1
|
||||
? getIt<UniversalService>(instanceName: prevDepName)
|
||||
: null,
|
||||
? getIt<UniversalService>(instanceName: prevDepName)
|
||||
: null,
|
||||
),
|
||||
instanceName: depName,
|
||||
);
|
||||
@@ -75,20 +78,19 @@ void Function(dynamic) getUniversalRegistration(
|
||||
() => UniversalServiceImpl(
|
||||
value: depName,
|
||||
dependency: level > 1
|
||||
? getIt<UniversalService>(instanceName: prevDepName)
|
||||
: null,
|
||||
? getIt<UniversalService>(instanceName: prevDepName)
|
||||
: null,
|
||||
),
|
||||
instanceName: depName,
|
||||
);
|
||||
break;
|
||||
case UniversalBindingMode.asyncStrategy:
|
||||
// getIt не поддерживает асинх. factory напрямую, но можно так:
|
||||
getIt.registerSingletonAsync<UniversalService>(
|
||||
() async => UniversalServiceImpl(
|
||||
value: depName,
|
||||
dependency: level > 1
|
||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
||||
: null,
|
||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
||||
: null,
|
||||
),
|
||||
instanceName: depName,
|
||||
);
|
||||
@@ -108,14 +110,11 @@ void Function(dynamic) getUniversalRegistration(
|
||||
getIt<UniversalService>(instanceName: depName),
|
||||
);
|
||||
}
|
||||
};
|
||||
} as Registration<TContainer>;
|
||||
}
|
||||
|
||||
// Riverpod
|
||||
if (adapter.runtimeType.toString().contains('RiverpodAdapter')) {
|
||||
// Регистрация Provider-ов по универсальному сценарию
|
||||
if (adapter is DIAdapter<Map<String, rp.ProviderBase<Object?>>> && adapter.runtimeType.toString().contains('RiverpodAdapter')) {
|
||||
return (providers) {
|
||||
// providers это Map<String, ProviderBase<Object?>>
|
||||
switch (scenario) {
|
||||
case UniversalScenario.register:
|
||||
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'reg', dependency: null));
|
||||
@@ -135,7 +134,6 @@ void Function(dynamic) getUniversalRegistration(
|
||||
));
|
||||
}
|
||||
}
|
||||
// Alias для последнего (универсальное имя)
|
||||
final depName = '${chainCount}_$nestingDepth';
|
||||
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => ref.watch(providers[depName] as rp.ProviderBase<UniversalService>));
|
||||
break;
|
||||
@@ -157,15 +155,14 @@ void Function(dynamic) getUniversalRegistration(
|
||||
});
|
||||
}
|
||||
}
|
||||
// Alias для последнего (универсальное имя)
|
||||
final depName = '${chainCount}_$nestingDepth';
|
||||
providers['UniversalService'] = rp.FutureProvider<UniversalService>((ref) async {
|
||||
return await ref.watch(providers[depName]!.future) as UniversalService;
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
} as Registration<TContainer>;
|
||||
}
|
||||
|
||||
throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}');
|
||||
throw UnsupportedError('Unknown DIAdapter type: [38;5;3m${adapter.runtimeType}[0m');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user