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 {
|
||||
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}.');
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue