refactor: adding rebound callbacks

This commit is contained in:
Patrick Stewart 2024-12-26 22:45:12 -07:00
parent 972c0424e1
commit d920180845
2 changed files with 179 additions and 0 deletions

View file

@ -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)) {

View 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)));
});
});
}