feat(benchmark_di): add resolve phases, eager/lazy scenarios, and adapter parity

This commit is contained in:
Klim
2026-04-22 20:33:07 +03:00
parent 59234b44c2
commit 4077ed8469
13 changed files with 383 additions and 257 deletions

View File

@@ -59,11 +59,16 @@ class UniversalChainModule extends Module {
switch (scenario) {
case UniversalScenario.register:
// Simple singleton registration.
bind<UniversalService>()
.toProvide(
() => UniversalServiceImpl(value: 'reg', dependency: null))
.singleton();
if (bindingMode == UniversalBindingMode.lazySingletonStrategy) {
bind<UniversalService>()
.toProvide(
() => UniversalServiceImpl(value: 'reg', dependency: null))
.singleton();
} else {
bind<UniversalService>()
.toInstance(
UniversalServiceImpl(value: 'reg', dependency: null));
}
break;
case UniversalScenario.named:
// Named factory registration for two distinct objects.
@@ -76,6 +81,8 @@ class UniversalChainModule extends Module {
break;
case UniversalScenario.chain:
// Chain of nested services, with dependency on previous level by name.
UniversalService? lastEagerInstance;
final Map<String, UniversalService> eagerInstances = {};
for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) {
for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) {
final chain = chainIndex + 1;
@@ -84,6 +91,17 @@ class UniversalChainModule extends Module {
final depName = '${chain}_$level';
switch (bindingMode) {
case UniversalBindingMode.singletonStrategy:
final instance = UniversalServiceImpl(
value: depName,
dependency: eagerInstances[prevDepName],
);
bind<UniversalService>()
.toInstance(instance)
.withName(depName);
eagerInstances[depName] = instance;
lastEagerInstance = instance;
break;
case UniversalBindingMode.lazySingletonStrategy:
bind<UniversalService>()
.toProvide(() => UniversalServiceImpl(
value: depName,
@@ -116,15 +134,19 @@ class UniversalChainModule extends Module {
}
}
}
// Регистрация алиаса без имени (на последний элемент цепочки)
final depName = '${chainCount}_$nestingDepth';
bind<UniversalService>()
.toProvide(
() => currentScope.resolve<UniversalService>(named: depName))
.singleton();
// Register unnamed alias for the last chain element.
if (bindingMode == UniversalBindingMode.lazySingletonStrategy) {
final depName = '${chainCount}_$nestingDepth';
bind<UniversalService>()
.toProvide(
() => currentScope.resolve<UniversalService>(named: depName))
.singleton();
} else if (lastEagerInstance != null) {
bind<UniversalService>().toInstance(lastEagerInstance);
}
break;
case UniversalScenario.override:
// handled at benchmark level, но алиас нужен прямо в этом scope!
// Handled at benchmark level, but alias is needed directly in this scope.
final depName = '${chainCount}_$nestingDepth';
bind<UniversalService>()
.toProvide(
@@ -189,7 +211,7 @@ class CherrypickDIAdapter extends DIAdapter<Scope> {
await CherryPick.closeRootScope();
_scope = null;
}
// SubScope teardown не требуется
// SubScope teardown is handled by the parent scope.
}
@override

View File

@@ -4,14 +4,14 @@ import 'package:benchmark_di/scenarios/universal_service.dart';
import 'package:get_it/get_it.dart';
import 'di_adapter.dart';
/// Универсальный DIAdapter для GetIt c поддержкой scopes и строгой типизацией.
/// DIAdapter for GetIt with scope support and strict typing.
class GetItAdapter extends DIAdapter<GetIt> {
late GetIt _getIt;
final String? _scopeName;
final bool _isSubScope;
bool _scopePushed = false;
/// Основной (root) и subScope-конструкторы.
/// Root and subScope constructors.
GetItAdapter({GetIt? instance, String? scopeName, bool isSubScope = false})
: _scopeName = scopeName,
_isSubScope = isSubScope {
@@ -23,7 +23,7 @@ class GetItAdapter extends DIAdapter<GetIt> {
@override
void setupDependencies(void Function(GetIt container) registration) {
if (_isSubScope) {
// Создаём scope через pushNewScope с init
// Create scope via pushNewScope with init
_getIt.pushNewScope(
scopeName: _scopeName,
init: (getIt) => registration(getIt),
@@ -41,7 +41,7 @@ class GetItAdapter extends DIAdapter<GetIt> {
@override
Future<T> resolveAsync<T extends Object>({String? named}) async =>
_getIt<T>(instanceName: named);
_getIt.getAsync<T>(instanceName: named);
@override
void teardown() {
@@ -92,8 +92,13 @@ class GetItAdapter extends DIAdapter<GetIt> {
}
break;
case UniversalScenario.register:
getIt.registerSingleton<UniversalService>(
UniversalServiceImpl(value: 'reg', dependency: null));
if (bindingMode == UniversalBindingMode.lazySingletonStrategy) {
getIt.registerLazySingleton<UniversalService>(
() => UniversalServiceImpl(value: 'reg', dependency: null));
} else {
getIt.registerSingleton<UniversalService>(
UniversalServiceImpl(value: 'reg', dependency: null));
}
break;
case UniversalScenario.named:
getIt.registerFactory<UniversalService>(
@@ -120,6 +125,17 @@ class GetItAdapter extends DIAdapter<GetIt> {
instanceName: depName,
);
break;
case UniversalBindingMode.lazySingletonStrategy:
getIt.registerLazySingleton<UniversalService>(
() => UniversalServiceImpl(
value: depName,
dependency: level > 1
? getIt<UniversalService>(instanceName: prevDepName)
: null,
),
instanceName: depName,
);
break;
case UniversalBindingMode.factoryStrategy:
getIt.registerFactory<UniversalService>(
() => UniversalServiceImpl(
@@ -154,9 +170,15 @@ class GetItAdapter extends DIAdapter<GetIt> {
if (scenario == UniversalScenario.chain ||
scenario == UniversalScenario.override) {
final depName = '${chainCount}_$nestingDepth';
getIt.registerSingleton<UniversalService>(
getIt<UniversalService>(instanceName: depName),
);
if (bindingMode == UniversalBindingMode.lazySingletonStrategy) {
getIt.registerLazySingleton<UniversalService>(
() => getIt<UniversalService>(instanceName: depName),
);
} else {
getIt.registerSingleton<UniversalService>(
getIt<UniversalService>(instanceName: depName),
);
}
}
};
}

View File

@@ -4,14 +4,11 @@ import 'package:benchmark_di/scenarios/universal_service.dart';
import 'package:kiwi/kiwi.dart';
import 'di_adapter.dart';
/// DIAdapter-для KiwiContainer с поддержкой universal benchmark сценариев.
/// DIAdapter for KiwiContainer with universal benchmark scenario support.
class KiwiAdapter extends DIAdapter<KiwiContainer> {
late KiwiContainer _container;
// ignore: unused_field
final bool _isSubScope;
KiwiAdapter({KiwiContainer? container, bool isSubScope = false})
: _isSubScope = isSubScope {
KiwiAdapter({KiwiContainer? container, bool isSubScope = false}) {
_container = container ?? KiwiContainer();
}
@@ -57,6 +54,8 @@ class KiwiAdapter extends DIAdapter<KiwiContainer> {
final depName = '${chain}_$level';
switch (bindingMode) {
case UniversalBindingMode.singletonStrategy:
case UniversalBindingMode.lazySingletonStrategy:
// Kiwi's registerSingleton is lazy (factory-based, cached on first resolve)
container.registerSingleton<UniversalService>(
(c) => UniversalServiceImpl(
value: depName,
@@ -75,7 +74,7 @@ class KiwiAdapter extends DIAdapter<KiwiContainer> {
name: depName);
break;
case UniversalBindingMode.asyncStrategy:
// Не поддерживается
// Not supported
break;
}
}
@@ -97,23 +96,12 @@ class KiwiAdapter extends DIAdapter<KiwiContainer> {
@override
T resolve<T extends Object>({String? named}) {
// Для asyncChain нужен resolve<Future<T>>
if (T.toString().startsWith('Future<')) {
return _container.resolve<T>(named);
} else {
return _container.resolve<T>(named);
}
return _container.resolve<T>(named);
}
@override
Future<T> resolveAsync<T extends Object>({String? named}) async {
if (T.toString().startsWith('Future<')) {
// resolve<Future<T>>, unwrap result
return Future.value(_container.resolve<T>(named));
} else {
// Для совместимости с chain/override
return Future.value(_container.resolve<T>(named));
}
Future<T> resolveAsync<T extends Object>({String? named}) {
return Future.value(_container.resolve<T>(named));
}
@override
@@ -123,7 +111,7 @@ class KiwiAdapter extends DIAdapter<KiwiContainer> {
@override
KiwiAdapter openSubScope(String name) {
// Возвращаем новый scoped контейнер (отдельный). Наследование не реализовано.
// Returns a new scoped container. Inheritance not implemented.
return KiwiAdapter(container: KiwiContainer.scoped(), isSubScope: true);
}

View File

@@ -29,8 +29,7 @@ class YxScopeAdapter extends DIAdapter<UniversalYxScopeContainer> {
@override
void teardown() {
// У yx_scope нет явного dispose на ScopeContainer, но можно добавить очистку Map/Deps если потребуется
// Ничего не делаем
_scope = UniversalYxScopeContainer();
}
@override
@@ -54,26 +53,13 @@ class YxScopeAdapter extends DIAdapter<UniversalYxScopeContainer> {
required UniversalBindingMode bindingMode,
}) {
if (scenario is UniversalScenario) {
if (scenario == UniversalScenario.asyncChain ||
bindingMode == UniversalBindingMode.asyncStrategy) {
throw UnsupportedError(
'YxScope does not support async dependencies or async binding scenarios.');
}
return (scope) {
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';
final dep = scope.dep<UniversalService>(
() => UniversalServiceImpl(
value: depName,
dependency: level > 1
? scope.depFor<UniversalService>(name: prevDepName).get
: null,
),
name: depName,
);
scope.register<UniversalService>(dep, name: depName);
}
}
break;
case UniversalScenario.register:
final dep = scope.dep<UniversalService>(
() => UniversalServiceImpl(value: 'reg', dependency: null),
@@ -113,6 +99,8 @@ class YxScopeAdapter extends DIAdapter<UniversalYxScopeContainer> {
case UniversalScenario.override:
// handled at benchmark level
break;
default:
break;
}
if (scenario == UniversalScenario.chain ||
scenario == UniversalScenario.override) {