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 {
final Reflector reflector;
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) {
if (_singletons.containsKey(type)) {
return _singletons[type] as T;
} else {
var reflectedType = reflector.reflectType(type);
var positional = [];
var named = <String, dynamic>{};
Container._child(this._parent) : reflector = _parent.reflector;
if (reflectedType is ReflectedClass) {
bool isDefault(String name) {
return name.isEmpty || name == reflectedType.name;
}
bool get isRoot => _parent == null;
var constructor = reflectedType.constructors.firstWhere(
(c) => isDefault(c.name),
orElse: () => throw new ReflectionException(
'${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'));
/// Creates a child [Container] that can define its own singletons and factories.
///
/// Use this to create children of a global "scope."
Container createChild() {
return new Container._child(this);
}
for (var param in constructor.parameters) {
var value = make(param.type.reflectedType);
/// Instantiates an instance of [T].
///
/// 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) {
named[param.name] = value;
} else {
positional.add(value);
}
}
// Find a singleton, if any.
var search = this;
return reflectedType.newInstance(
isDefault(constructor.name) ? '' : constructor.name,
positional,
named, []);
while (search != null) {
if (search._singletons.containsKey(type)) {
return search._singletons[type] as T;
} else {
throw new ReflectionException(
'$type is not a class, and therefore cannot be instantiated.');
search = search._parent;
}
}
// 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)) {
throw new StateError(
'This container already has a singleton for ${as ?? object.runtimeType}.');

View file

@ -1,7 +1,7 @@
name: angel_container
version: 1.0.0-alpha
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
environment:
sdk: ">=1.8.0 <3.0.0"

View file

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