Container hierarchy support
This commit is contained in:
parent
83bb384fbc
commit
c894b7a411
5 changed files with 148 additions and 35 deletions
5
angel_container/CHANGELOG.md
Normal file
5
angel_container/CHANGELOG.md
Normal 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.
|
55
angel_container/example/main.dart
Normal file
55
angel_container/example/main.dart
Normal 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.');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}.');
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue