refactor: adding rebound callbacks
This commit is contained in:
parent
972c0424e1
commit
d920180845
2 changed files with 179 additions and 0 deletions
|
@ -30,6 +30,13 @@ class Container {
|
|||
/// The container's service extenders
|
||||
final Map<Type, List<dynamic Function(dynamic, Container)>> _extenders = {};
|
||||
|
||||
/// The container's rebound callbacks
|
||||
final Map<Type, List<void Function(dynamic, Container)>> _reboundCallbacks =
|
||||
{};
|
||||
|
||||
/// The container's refreshing instances
|
||||
final Set<Type> _refreshing = {};
|
||||
|
||||
/// The container's contextual bindings
|
||||
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
||||
|
||||
|
@ -883,6 +890,67 @@ class Container {
|
|||
return instance;
|
||||
}
|
||||
|
||||
/// Register a callback to be run when a type is rebound.
|
||||
///
|
||||
/// The callback will be invoked whenever the type's binding is replaced
|
||||
/// or when refresh() is called on the type.
|
||||
///
|
||||
/// ```dart
|
||||
/// container.rebinding<Logger>((logger, container) {
|
||||
/// print('Logger was rebound');
|
||||
/// });
|
||||
/// ```
|
||||
void rebinding<T>(
|
||||
void Function(dynamic instance, Container container) callback) {
|
||||
_reboundCallbacks.putIfAbsent(T, () => []).add(callback);
|
||||
}
|
||||
|
||||
/// Refresh an instance in the container.
|
||||
///
|
||||
/// This will create a new instance and trigger any rebound callbacks.
|
||||
/// If the instance is a singleton, it will be replaced in the container.
|
||||
///
|
||||
/// ```dart
|
||||
/// container.refresh<Logger>();
|
||||
/// ```
|
||||
T refresh<T>() {
|
||||
if (_refreshing.contains(T)) {
|
||||
throw CircularDependencyException(
|
||||
'Circular dependency detected while refreshing $T');
|
||||
}
|
||||
|
||||
_refreshing.add(T);
|
||||
try {
|
||||
// Create new instance
|
||||
var instance = make<T>();
|
||||
|
||||
// If it's a singleton, replace it
|
||||
if (_singletons.containsKey(T)) {
|
||||
_singletons[T] = instance;
|
||||
}
|
||||
|
||||
// Fire rebound callbacks
|
||||
_fireReboundCallbacks(T, instance);
|
||||
|
||||
return instance;
|
||||
} finally {
|
||||
_refreshing.remove(T);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fire the rebound callbacks for a type.
|
||||
void _fireReboundCallbacks(Type type, dynamic instance) {
|
||||
Container? search = this;
|
||||
while (search != null) {
|
||||
if (search._reboundCallbacks.containsKey(type)) {
|
||||
for (var callback in search._reboundCallbacks[type]!) {
|
||||
callback(instance, this);
|
||||
}
|
||||
}
|
||||
search = search._parent;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if we're in danger of a circular dependency.
|
||||
void _checkCircularDependency(Type type) {
|
||||
if (_buildStack.contains(type)) {
|
||||
|
|
111
incubation/container/container/test/rebound_test.dart
Normal file
111
incubation/container/container/test/rebound_test.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
import 'package:platformed_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class Service {
|
||||
String value = 'initial';
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Rebound Tests', () {
|
||||
test('rebinding callback is called when singleton is refreshed', () {
|
||||
var callCount = 0;
|
||||
var lastInstance;
|
||||
|
||||
container.registerSingleton<Service>(Service());
|
||||
container.rebinding<Service>((instance, container) {
|
||||
callCount++;
|
||||
lastInstance = instance;
|
||||
});
|
||||
|
||||
var refreshed = container.refresh<Service>();
|
||||
expect(callCount, equals(1));
|
||||
expect(lastInstance, equals(refreshed));
|
||||
expect(container.make<Service>(), equals(refreshed));
|
||||
});
|
||||
|
||||
test('multiple rebound callbacks are called in order', () {
|
||||
var order = [];
|
||||
container.registerSingleton<Service>(Service());
|
||||
|
||||
container.rebinding<Service>((instance, container) {
|
||||
order.add(1);
|
||||
});
|
||||
|
||||
container.rebinding<Service>((instance, container) {
|
||||
order.add(2);
|
||||
});
|
||||
|
||||
container.refresh<Service>();
|
||||
expect(order, equals([1, 2]));
|
||||
});
|
||||
|
||||
test('child container inherits parent rebound callbacks', () {
|
||||
var parentCallCount = 0;
|
||||
var childCallCount = 0;
|
||||
|
||||
container.registerSingleton<Service>(Service());
|
||||
container.rebinding<Service>((instance, container) {
|
||||
parentCallCount++;
|
||||
});
|
||||
|
||||
var child = container.createChild();
|
||||
child.rebinding<Service>((instance, container) {
|
||||
childCallCount++;
|
||||
});
|
||||
|
||||
child.refresh<Service>();
|
||||
expect(parentCallCount, equals(1));
|
||||
expect(childCallCount, equals(1));
|
||||
});
|
||||
|
||||
test('refresh throws on circular dependency', () {
|
||||
container.registerSingleton<Service>(Service());
|
||||
container.rebinding<Service>((instance, container) {
|
||||
container.refresh<Service>();
|
||||
});
|
||||
|
||||
expect(() => container.refresh<Service>(),
|
||||
throwsA(isA<CircularDependencyException>()));
|
||||
});
|
||||
|
||||
test('refresh creates new instance for factory binding', () {
|
||||
var count = 0;
|
||||
container.registerFactory<Service>((c) {
|
||||
count++;
|
||||
return Service();
|
||||
});
|
||||
|
||||
var first = container.make<Service>();
|
||||
var second = container.refresh<Service>();
|
||||
|
||||
expect(count, equals(2));
|
||||
expect(first, isNot(equals(second)));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue