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
|
/// The container's service extenders
|
||||||
final Map<Type, List<dynamic Function(dynamic, Container)>> _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
|
/// The container's contextual bindings
|
||||||
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
||||||
|
|
||||||
|
@ -883,6 +890,67 @@ class Container {
|
||||||
return instance;
|
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.
|
/// Check if we're in danger of a circular dependency.
|
||||||
void _checkCircularDependency(Type type) {
|
void _checkCircularDependency(Type type) {
|
||||||
if (_buildStack.contains(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