refactor: full generic DIAdapter workflow & universalRegistration abstraction

- Made UniversalChainBenchmark and UniversalChainAsyncBenchmark fully generic with strong typing for DIAdapter<TContainer>
- Moved all universalRegistration logic inside adapters; removed global function and typedef
- Replaced dynamic/object-based registration with type-safe generic contracts end-to-end
- Updated CLI and usage: all DI and scenarios are invoked via type-safe generics
- Fixed all analysis errors, Riverpod async/future usages, and imports for full Dart 3 compatibility
- All benchmarks fully pass for Cherrypick, GetIt, and Riverpod adapters
This commit is contained in:
Sergey Penkovsky
2025-08-07 14:11:29 +03:00
parent 54446868e4
commit 56bdb3946e
8 changed files with 223 additions and 204 deletions

View File

@@ -2,10 +2,9 @@ import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:benchmark_di/di_adapters/di_adapter.dart';
import 'package:benchmark_di/scenarios/universal_chain_module.dart';
import 'package:benchmark_di/scenarios/universal_service.dart';
import 'package:benchmark_di/scenarios/di_universal_registration.dart';
class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase {
final DIAdapter di;
class UniversalChainAsyncBenchmark<TContainer> extends AsyncBenchmarkBase {
final DIAdapter<TContainer> di;
final int chainCount;
final int nestingDepth;
final UniversalBindingMode mode;
@@ -19,8 +18,7 @@ class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase {
@override
Future<void> setup() async {
di.setupDependencies(getUniversalRegistration(
di,
di.setupDependencies(di.universalRegistration(
chainCount: chainCount,
nestingDepth: nestingDepth,
bindingMode: mode,

View File

@@ -2,15 +2,14 @@ import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:benchmark_di/di_adapters/di_adapter.dart';
import 'package:benchmark_di/scenarios/universal_chain_module.dart';
import 'package:benchmark_di/scenarios/universal_service.dart';
import 'package:benchmark_di/scenarios/di_universal_registration.dart';
class UniversalChainBenchmark extends BenchmarkBase {
final DIAdapter _di;
class UniversalChainBenchmark<TContainer> extends BenchmarkBase {
final DIAdapter<TContainer> _di;
final int chainCount;
final int nestingDepth;
final UniversalBindingMode mode;
final UniversalScenario scenario;
DIAdapter? _childDi;
DIAdapter<TContainer>? _childDi;
UniversalChainBenchmark(
this._di, {
@@ -24,25 +23,22 @@ class UniversalChainBenchmark extends BenchmarkBase {
void setup() {
switch (scenario) {
case UniversalScenario.override:
_di.setupDependencies(getUniversalRegistration(
_di,
_di.setupDependencies(_di.universalRegistration(
chainCount: chainCount,
nestingDepth: nestingDepth,
bindingMode: UniversalBindingMode.singletonStrategy,
scenario: UniversalScenario.chain,
));
_childDi = _di.openSubScope('child');
_childDi!.setupDependencies(getUniversalRegistration(
_childDi!,
_childDi!.setupDependencies(_childDi!.universalRegistration(
chainCount: chainCount,
nestingDepth: nestingDepth,
bindingMode: UniversalBindingMode.singletonStrategy,
scenario: UniversalScenario.chain, // критично: цепочку, а не просто alias!
scenario: UniversalScenario.chain,
));
break;
default:
_di.setupDependencies(getUniversalRegistration(
_di,
_di.setupDependencies(_di.universalRegistration(
chainCount: chainCount,
nestingDepth: nestingDepth,
bindingMode: mode,

View File

@@ -1,6 +1,9 @@
import 'dart:math';
import 'package:benchmark_di/cli/report/markdown_report.dart';
import 'package:cherrypick/cherrypick.dart';
import 'package:get_it/get_it.dart';
import 'package:riverpod/riverpod.dart' as rp;
import '../scenarios/universal_chain_module.dart';
import 'report/pretty_report.dart';
@@ -33,7 +36,7 @@ class BenchmarkCliRunner {
if (config.di == 'getit') {
final di = GetItAdapter();
if (scenario == UniversalScenario.asyncChain) {
final benchAsync = UniversalChainAsyncBenchmark(di,
final benchAsync = UniversalChainAsyncBenchmark<GetIt>(di,
chainCount: c, nestingDepth: d, mode: mode,
);
benchResult = await BenchmarkRunner.runAsync(
@@ -42,7 +45,7 @@ class BenchmarkCliRunner {
repeats: config.repeats,
);
} else {
final benchSync = UniversalChainBenchmark(di,
final benchSync = UniversalChainBenchmark<GetIt>(di,
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
);
benchResult = await BenchmarkRunner.runSync(
@@ -54,7 +57,7 @@ class BenchmarkCliRunner {
} else if (config.di == 'riverpod') {
final di = RiverpodAdapter();
if (scenario == UniversalScenario.asyncChain) {
final benchAsync = UniversalChainAsyncBenchmark(di,
final benchAsync = UniversalChainAsyncBenchmark<Map<String, rp.ProviderBase<Object?>>>(di,
chainCount: c, nestingDepth: d, mode: mode,
);
benchResult = await BenchmarkRunner.runAsync(
@@ -63,7 +66,7 @@ class BenchmarkCliRunner {
repeats: config.repeats,
);
} else {
final benchSync = UniversalChainBenchmark(di,
final benchSync = UniversalChainBenchmark<Map<String, rp.ProviderBase<Object?>>>(di,
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
);
benchResult = await BenchmarkRunner.runSync(
@@ -75,7 +78,7 @@ class BenchmarkCliRunner {
} else {
final di = CherrypickDIAdapter();
if (scenario == UniversalScenario.asyncChain) {
final benchAsync = UniversalChainAsyncBenchmark(di,
final benchAsync = UniversalChainAsyncBenchmark<Scope>(di,
chainCount: c, nestingDepth: d, mode: mode,
);
benchResult = await BenchmarkRunner.runAsync(
@@ -84,7 +87,7 @@ class BenchmarkCliRunner {
repeats: config.repeats,
);
} else {
final benchSync = UniversalChainBenchmark(di,
final benchSync = UniversalChainBenchmark<Scope>(di,
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
);
benchResult = await BenchmarkRunner.runSync(

View File

@@ -1,7 +1,7 @@
import 'package:cherrypick/cherrypick.dart';
import 'di_adapter.dart';
import '../scenarios/universal_chain_module.dart';
/// Универсальный DIAdapter для CherryPick с поддержкой subScope без дублирования логики.
class CherrypickDIAdapter extends DIAdapter<Scope> {
Scope? _scope;
final bool _isSubScope;
@@ -16,6 +16,28 @@ class CherrypickDIAdapter extends DIAdapter<Scope> {
registration(_scope!);
}
@override
Registration<Scope> universalRegistration<S extends Enum>({
required S scenario,
required int chainCount,
required int nestingDepth,
required UniversalBindingMode bindingMode,
}) {
if (scenario is UniversalScenario) {
return (scope) {
scope.installModules([
UniversalChainModule(
chainCount: chainCount,
nestingDepth: nestingDepth,
bindingMode: bindingMode,
scenario: scenario,
),
]);
};
}
throw UnsupportedError('Scenario $scenario not supported by CherrypickDIAdapter');
}
@override
T resolve<T extends Object>({String? named}) =>
named == null ? _scope!.resolve<T>() : _scope!.resolve<T>(named: named);

View File

@@ -1,9 +1,21 @@
import 'package:benchmark_di/scenarios/universal_chain_module.dart';
/// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации.
/// Теперь для каждого адаптера задаём строгий generic тип контейнера.
typedef Registration<TContainer> = void Function(TContainer);
abstract class DIAdapter<TContainer> {
/// Устанавливает зависимости с помощью строго типизированного контейнера.
void setupDependencies(void Function(TContainer container) registration);
/// Возвращает типобезопасную функцию регистрации зависимостей под конкретный сценарий.
Registration<TContainer> universalRegistration<S extends Enum>({
required S scenario,
required int chainCount,
required int nestingDepth,
required UniversalBindingMode bindingMode,
});
/// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется).
T resolve<T extends Object>({String? named});

View File

@@ -1,3 +1,5 @@
import 'package:benchmark_di/scenarios/universal_chain_module.dart';
import 'package:benchmark_di/scenarios/universal_service.dart';
import 'package:get_it/get_it.dart';
import 'di_adapter.dart';
@@ -58,4 +60,96 @@ class GetItAdapter extends DIAdapter<GetIt> {
Future<void> waitForAsyncReady() async {
await _getIt.allReady();
}
@override
Registration<GetIt> universalRegistration<S extends Enum>({
required S scenario,
required int chainCount,
required int nestingDepth,
required UniversalBindingMode bindingMode,
}) {
if (scenario is UniversalScenario) {
return (getIt) {
switch (scenario) {
case UniversalScenario.asyncChain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
getIt.registerSingletonAsync<UniversalService>(
() async {
final prev = level > 1
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
: null;
return UniversalServiceImpl(value: depName, dependency: prev);
},
instanceName: depName,
);
}
}
break;
case UniversalScenario.register:
getIt.registerSingleton<UniversalService>(UniversalServiceImpl(value: 'reg', dependency: null));
break;
case UniversalScenario.named:
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1');
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2');
break;
case UniversalScenario.chain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
switch (bindingMode) {
case UniversalBindingMode.singletonStrategy:
getIt.registerSingleton<UniversalService>(
UniversalServiceImpl(
value: depName,
dependency: level > 1
? getIt<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
case UniversalBindingMode.factoryStrategy:
getIt.registerFactory<UniversalService>(
() => UniversalServiceImpl(
value: depName,
dependency: level > 1
? getIt<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
case UniversalBindingMode.asyncStrategy:
getIt.registerSingletonAsync<UniversalService>(
() async => UniversalServiceImpl(
value: depName,
dependency: level > 1
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
}
}
}
break;
case UniversalScenario.override:
// handled at benchmark level
break;
}
if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) {
final depName = '${chainCount}_$nestingDepth';
getIt.registerSingleton<UniversalService>(
getIt<UniversalService>(instanceName: depName),
);
}
};
}
throw UnsupportedError('Scenario $scenario not supported by GetItAdapter');
}
}

View File

@@ -1,28 +1,30 @@
import 'package:riverpod/riverpod.dart';
import 'package:benchmark_di/scenarios/universal_chain_module.dart';
import 'package:benchmark_di/scenarios/universal_service.dart';
import 'package:riverpod/riverpod.dart' as rp;
import 'di_adapter.dart';
/// Унифицированный DIAdapter для Riverpod с поддержкой scopes и строгой типизацией.
class RiverpodAdapter extends DIAdapter<Map<String, ProviderBase<Object?>>> {
ProviderContainer? _container;
final Map<String, ProviderBase<Object?>> _namedProviders;
final ProviderContainer? _parent;
class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
rp.ProviderContainer? _container;
final Map<String, rp.ProviderBase<Object?>> _namedProviders;
final rp.ProviderContainer? _parent;
final bool _isSubScope;
RiverpodAdapter({
ProviderContainer? container,
Map<String, ProviderBase<Object?>>? providers,
ProviderContainer? parent,
rp.ProviderContainer? container,
Map<String, rp.ProviderBase<Object?>>? providers,
rp.ProviderContainer? parent,
bool isSubScope = false,
}) : _container = container,
_namedProviders = providers ?? <String, ProviderBase<Object?>>{},
_namedProviders = providers ?? <String, rp.ProviderBase<Object?>>{},
_parent = parent,
_isSubScope = isSubScope;
@override
void setupDependencies(void Function(Map<String, ProviderBase<Object?>> container) registration) {
void setupDependencies(void Function(Map<String, rp.ProviderBase<Object?>> container) registration) {
_container ??= _parent == null
? ProviderContainer()
: ProviderContainer(parent: _parent);
? rp.ProviderContainer()
: rp.ProviderContainer(parent: _parent);
registration(_namedProviders);
}
@@ -59,7 +61,7 @@ class RiverpodAdapter extends DIAdapter<Map<String, ProviderBase<Object?>>> {
@override
RiverpodAdapter openSubScope(String name) {
final newContainer = ProviderContainer(parent: _container);
final newContainer = rp.ProviderContainer(parent: _container);
return RiverpodAdapter(
container: newContainer,
providers: Map.of(_namedProviders),
@@ -73,4 +75,64 @@ class RiverpodAdapter extends DIAdapter<Map<String, ProviderBase<Object?>>> {
// Riverpod синхронный по умолчанию.
return;
}
@override
Registration<Map<String, rp.ProviderBase<Object?>>> universalRegistration<S extends Enum>({
required S scenario,
required int chainCount,
required int nestingDepth,
required UniversalBindingMode bindingMode,
}) {
if (scenario is UniversalScenario) {
return (providers) {
switch (scenario) {
case UniversalScenario.register:
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'reg', dependency: null));
break;
case UniversalScenario.named:
providers['impl1'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl1'));
providers['impl2'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl2'));
break;
case UniversalScenario.chain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
providers[depName] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(
value: depName,
dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase<UniversalService>) : null,
));
}
}
final depName = '${chainCount}_$nestingDepth';
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => ref.watch(providers[depName] as rp.ProviderBase<UniversalService>));
break;
case UniversalScenario.override:
// handled at benchmark level
break;
case UniversalScenario.asyncChain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
providers[depName] = rp.FutureProvider<UniversalService>((ref) async {
return UniversalServiceImpl(
value: depName,
dependency: level > 1
? await ref.watch((providers[prevDepName] as rp.FutureProvider<UniversalService>).future) as UniversalService?
: null,
);
});
}
}
final depName = '${chainCount}_$nestingDepth';
providers['UniversalService'] = rp.FutureProvider<UniversalService>((ref) async {
return await ref.watch((providers[depName] as rp.FutureProvider<UniversalService>).future);
});
break;
}
};
}
throw UnsupportedError('Scenario $scenario not supported by RiverpodAdapter');
}
}

View File

@@ -1,168 +0,0 @@
import 'package:benchmark_di/scenarios/universal_service.dart';
import '../di_adapters/di_adapter.dart';
import '../di_adapters/cherrypick_adapter.dart';
import '../di_adapters/get_it_adapter.dart';
import 'universal_chain_module.dart';
import 'package:riverpod/riverpod.dart' as rp;
/// Унифицированный generic-колбэк для регистрации зависимостей,
/// подходящий под выбранный DI-адаптер.
typedef Registration<TContainer> = void Function(TContainer);
Registration<TContainer> getUniversalRegistration<TContainer>(
DIAdapter<TContainer> adapter, {
required int chainCount,
required int nestingDepth,
required UniversalBindingMode bindingMode,
required UniversalScenario scenario,
}) {
if (adapter is CherrypickDIAdapter) {
return (scope) {
scope.installModules([
UniversalChainModule(
chainCount: chainCount,
nestingDepth: nestingDepth,
bindingMode: bindingMode,
scenario: scenario,
),
]);
} as Registration<TContainer>;
}
if (adapter is GetItAdapter) {
return (getIt) {
switch (scenario) {
case UniversalScenario.asyncChain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
getIt.registerSingletonAsync<UniversalService>(
() async {
final prev = level > 1
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
: null;
return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?);
},
instanceName: depName,
);
}
}
break;
case UniversalScenario.register:
getIt.registerSingleton<UniversalService>(UniversalServiceImpl(value: 'reg', dependency: null));
break;
case UniversalScenario.named:
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1');
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2');
break;
case UniversalScenario.chain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
switch (bindingMode) {
case UniversalBindingMode.singletonStrategy:
getIt.registerSingleton<UniversalService>(
UniversalServiceImpl(
value: depName,
dependency: level > 1
? getIt<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
case UniversalBindingMode.factoryStrategy:
getIt.registerFactory<UniversalService>(
() => UniversalServiceImpl(
value: depName,
dependency: level > 1
? getIt<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
case UniversalBindingMode.asyncStrategy:
getIt.registerSingletonAsync<UniversalService>(
() async => UniversalServiceImpl(
value: depName,
dependency: level > 1
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
}
}
}
break;
case UniversalScenario.override:
// handled at benchmark level
break;
}
// UniversalService alias (без имени) для chain/override-сценариев
if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) {
final depName = '${chainCount}_$nestingDepth';
getIt.registerSingleton<UniversalService>(
getIt<UniversalService>(instanceName: depName),
);
}
} as Registration<TContainer>;
}
if (adapter is DIAdapter<Map<String, rp.ProviderBase<Object?>>> && adapter.runtimeType.toString().contains('RiverpodAdapter')) {
return (providers) {
switch (scenario) {
case UniversalScenario.register:
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'reg', dependency: null));
break;
case UniversalScenario.named:
providers['impl1'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl1'));
providers['impl2'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl2'));
break;
case UniversalScenario.chain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
providers[depName] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(
value: depName,
dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase<UniversalService>) : null,
));
}
}
final depName = '${chainCount}_$nestingDepth';
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => ref.watch(providers[depName] as rp.ProviderBase<UniversalService>));
break;
case UniversalScenario.override:
// handled at benchmark level
break;
case UniversalScenario.asyncChain:
for (int chain = 1; chain <= chainCount; chain++) {
for (int level = 1; level <= nestingDepth; level++) {
final prevDepName = '${chain}_${level - 1}';
final depName = '${chain}_$level';
providers[depName] = rp.FutureProvider<UniversalService>((ref) async {
return UniversalServiceImpl(
value: depName,
dependency: level > 1
? await ref.watch(providers[prevDepName]!.future) as UniversalService?
: null,
);
});
}
}
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}');
}