diff --git a/angel_container/CHANGELOG.md b/angel_container/CHANGELOG.md new file mode 100644 index 00000000..7c1f055f --- /dev/null +++ b/angel_container/CHANGELOG.md @@ -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. \ No newline at end of file diff --git a/angel_container/example/main.dart b/angel_container/example/main.dart new file mode 100644 index 00000000..d8aebd10 --- /dev/null +++ b/angel_container/example/main.dart @@ -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(40)); + + // Register a factory that creates a truck. + container.registerFactory((container) { + return _TruckImpl(container.make()); + }); + + // Use `make` to create an instance. + var truck = container.make(); + + // 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((container) { + return _TruckImpl(Engine(5666)); + }); + + // Make a truck with 5666 HP. + childContainer.make().drive(); + + // However, calling `make` will return the Engine singleton we created above. + print(childContainer.make().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.'); + } +} diff --git a/angel_container/lib/src/container.dart b/angel_container/lib/src/container.dart index f8178aef..621fc4d7 100644 --- a/angel_container/lib/src/container.dart +++ b/angel_container/lib/src/container.dart @@ -4,49 +4,98 @@ import 'reflector.dart'; class Container { final Reflector reflector; final Map _singletons = {}; + final Map _factories = {}; + final Container _parent; - Container(this.reflector); + Container(this.reflector) : _parent = null; - T make(Type type) { - if (_singletons.containsKey(type)) { - return _singletons[type] as T; - } else { - var reflectedType = reflector.reflectType(type); - var positional = []; - var named = {}; + 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([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 = {}; + + 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 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 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}.'); diff --git a/angel_container/pubspec.yaml b/angel_container/pubspec.yaml index 9f5a7521..1c63d6fd 100644 --- a/angel_container/pubspec.yaml +++ b/angel_container/pubspec.yaml @@ -1,7 +1,7 @@ name: angel_container version: 1.0.0-alpha author: Tobe O -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" diff --git a/angel_container/test/common.dart b/angel_container/test/common.dart index bb346ee4..d27969d5 100644 --- a/angel_container/test/common.dart +++ b/angel_container/test/common.dart @@ -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(), 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(); expect(lower.lowercaseName, blaziken.name.toLowerCase()); });