Container hierarchy support

This commit is contained in:
Tobe O 2018-08-20 00:40:30 -04:00
parent 83bb384fbc
commit c894b7a411
5 changed files with 148 additions and 35 deletions

View file

@ -0,0 +1,5 @@
# 1.0.0-alpha.1
* Allow omission of the first argument of `Container.make`, to use
a generic type argument instead.
* `singleton` -> `registerSingleton`
* Add `createChild`, and support hierarchical containers.

View file

@ -0,0 +1,55 @@
import 'package:angel_container/angel_container.dart';
import 'package:angel_container/mirrors.dart';
void main() {
// Create a container instance.
var container = new Container(MirrorsReflector());
// Register a singleton.
container.registerSingleton<Engine>(Engine(40));
// Register a factory that creates a truck.
container.registerFactory<Truck>((container) {
return _TruckImpl(container.make<Engine>());
});
// Use `make` to create an instance.
var truck = container.make<Truck>();
// Should print: 'Vroom! I have 40 horsepower in my engine.'
truck.drive();
// We can make a child container with its own factory.
var childContainer = container.createChild();
childContainer.registerFactory<Truck>((container) {
return _TruckImpl(Engine(5666));
});
// Make a truck with 5666 HP.
childContainer.make<Truck>().drive();
// However, calling `make<Engine>` will return the Engine singleton we created above.
print(childContainer.make<Engine>().horsePower);
}
abstract class Truck {
void drive();
}
class Engine {
final int horsePower;
Engine(this.horsePower);
}
class _TruckImpl implements Truck {
final Engine engine;
_TruckImpl(this.engine);
@override
void drive() {
print('Vroom! I have ${engine.horsePower} horsepower in my engine.');
}
}

View file

@ -4,49 +4,98 @@ import 'reflector.dart';
class Container { class Container {
final Reflector reflector; final Reflector reflector;
final Map<Type, dynamic> _singletons = {}; final Map<Type, dynamic> _singletons = {};
final Map<Type, dynamic Function(Container)> _factories = {};
final Container _parent;
Container(this.reflector); Container(this.reflector) : _parent = null;
T make<T>(Type type) { Container._child(this._parent) : reflector = _parent.reflector;
if (_singletons.containsKey(type)) {
return _singletons[type] as T;
} else {
var reflectedType = reflector.reflectType(type);
var positional = [];
var named = <String, dynamic>{};
if (reflectedType is ReflectedClass) { bool get isRoot => _parent == null;
bool isDefault(String name) {
return name.isEmpty || name == reflectedType.name;
}
var constructor = reflectedType.constructors.firstWhere( /// Creates a child [Container] that can define its own singletons and factories.
(c) => isDefault(c.name), ///
orElse: () => throw new ReflectionException( /// Use this to create children of a global "scope."
'${reflectedType.name} has no default constructor, and therefore cannot be instantiated.')); Container createChild() {
return new Container._child(this);
}
for (var param in constructor.parameters) { /// Instantiates an instance of [T].
var value = make(param.type.reflectedType); ///
/// In contexts where a static generic type cannot be used, use
/// the [type] argument, instead of [T].
T make<T>([Type type]) {
type ??= T;
if (param.isNamed) { // Find a singleton, if any.
named[param.name] = value; var search = this;
} else {
positional.add(value);
}
}
return reflectedType.newInstance( while (search != null) {
isDefault(constructor.name) ? '' : constructor.name, if (search._singletons.containsKey(type)) {
positional, return search._singletons[type] as T;
named, []);
} else { } else {
throw new ReflectionException( search = search._parent;
'$type is not a class, and therefore cannot be instantiated.');
} }
} }
// Find a factory, if any.
search = this;
while (search != null) {
if (search._factories.containsKey(type)) {
return search._factories[type](this) as T;
} else {
search = search._parent;
}
}
var reflectedType = reflector.reflectType(type);
var positional = [];
var named = <String, dynamic>{};
if (reflectedType is ReflectedClass) {
bool isDefault(String name) {
return name.isEmpty || name == reflectedType.name;
}
var constructor = reflectedType.constructors.firstWhere(
(c) => isDefault(c.name),
orElse: () => throw new ReflectionException(
'${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'));
for (var param in constructor.parameters) {
var value = make(param.type.reflectedType);
if (param.isNamed) {
named[param.name] = value;
} else {
positional.add(value);
}
}
return reflectedType.newInstance(
isDefault(constructor.name) ? '' : constructor.name,
positional,
named, []);
} else {
throw new ReflectionException(
'$type is not a class, and therefore cannot be instantiated.');
}
} }
void singleton(Object object, {Type as}) { void registerFactory<T>(T Function(Container) f, {Type as}) {
as ??= T;
if (_factories.containsKey(as)) {
throw new StateError('This container already has a factory for $as.');
}
_factories[as] = f;
}
void registerSingleton<T>(T object, {Type as}) {
as ??= T == dynamic ? as : T;
if (_singletons.containsKey(as ?? object.runtimeType)) { if (_singletons.containsKey(as ?? object.runtimeType)) {
throw new StateError( throw new StateError(
'This container already has a singleton for ${as ?? object.runtimeType}.'); 'This container already has a singleton for ${as ?? object.runtimeType}.');

View file

@ -1,7 +1,7 @@
name: angel_container name: angel_container
version: 1.0.0-alpha version: 1.0.0-alpha
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
description: "A better IoC container for Angel, ultimately allowing Angel to be used without dart:mirrors." description: "A better IoC container and dependency injector for Angel, ultimately allowing Angel to be used without dart:mirrors."
homepage: https://github.com/angel-dart/container.git homepage: https://github.com/angel-dart/container.git
environment: environment:
sdk: ">=1.8.0 <3.0.0" sdk: ">=1.8.0 <3.0.0"

View file

@ -7,20 +7,24 @@ void testReflector(Reflector reflector) {
setUp(() { setUp(() {
container = new Container(reflector); container = new Container(reflector);
container.singleton(blaziken); container.registerSingleton(blaziken);
}); });
test('make on singleton type returns singleton', () { test('make on singleton type returns singleton', () {
expect(container.make(Pokemon), blaziken); expect(container.make(Pokemon), blaziken);
}); });
test('make with generic returns same as make with explicit type', () {
expect(container.make<Pokemon>(), blaziken);
});
test('make on aliased singleton returns singleton', () { test('make on aliased singleton returns singleton', () {
container.singleton(blaziken, as: StateError); container.registerSingleton(blaziken, as: StateError);
expect(container.make(StateError), blaziken); expect(container.make(StateError), blaziken);
}); });
test('constructor injects singleton', () { test('constructor injects singleton', () {
var lower = container.make(LowerPokemon) as LowerPokemon; var lower = container.make<LowerPokemon>();
expect(lower.lowercaseName, blaziken.name.toLowerCase()); expect(lower.lowercaseName, blaziken.name.toLowerCase());
}); });