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,13 +4,51 @@ 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; bool get isRoot => _parent == null;
/// 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);
}
/// 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;
// Find a singleton, if any.
var search = this;
while (search != null) {
if (search._singletons.containsKey(type)) {
return search._singletons[type] as T;
} else { } else {
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 reflectedType = reflector.reflectType(type);
var positional = []; var positional = [];
var named = <String, dynamic>{}; var named = <String, dynamic>{};
@ -44,9 +82,20 @@ class Container {
'$type is not a class, and therefore cannot be instantiated.'); '$type is not a class, and therefore cannot be instantiated.');
} }
} }
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.');
} }
void singleton(Object object, {Type 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());
}); });