From d313cac42f1060fc40e99ca8963eaa57283e0c9f Mon Sep 17 00:00:00 2001 From: vsavkin Date: Thu, 9 Oct 2014 11:35:13 -0400 Subject: [PATCH] refactor(injector): change toFactory to use reflector to construct dependencies --- modules/di/src/binding.js | 17 ++++++++++------- modules/di/src/exceptions.js | 4 ++-- modules/di/src/reflector.dart | 23 +++++++++++++++++------ modules/di/src/reflector.es6 | 14 +++++++------- modules/di/test/di/async_spec.js | 16 ++++++++-------- modules/di/test/di/injector_spec.js | 19 +++++++++++++++++-- 6 files changed, 61 insertions(+), 32 deletions(-) diff --git a/modules/di/src/binding.js b/modules/di/src/binding.js index 0e5082095c..5a326dd807 100644 --- a/modules/di/src/binding.js +++ b/modules/di/src/binding.js @@ -1,4 +1,4 @@ -import {FIELD, Type, bool} from 'facade/lang'; +import {FIELD, Type, bool, isBlank} from 'facade/lang'; import {List, MapWrapper, ListWrapper} from 'facade/collection'; import {reflector} from './reflector'; import {Key} from './key'; @@ -13,6 +13,7 @@ export class Dependency { this.lazy = lazy; } } + export class Binding { constructor(key:Key, factory:Function, dependencies:List, providedAsFuture:bool) { this.key = key; @@ -49,25 +50,27 @@ export class BindingBuilder { ); } - toFactory(dependencies:List, factoryFunction:Function):Binding { + toFactory(factoryFunction:Function, {dependencies=null}={}):Binding { return new Binding( Key.get(this.token), reflector.convertToFactory(factoryFunction), - this._constructDependencies(dependencies), + this._constructDependencies(factoryFunction, dependencies), false ); } - toAsyncFactory(dependencies:List, factoryFunction:Function):Binding { + toAsyncFactory(factoryFunction:Function, {dependencies=null}={}):Binding { return new Binding( Key.get(this.token), reflector.convertToFactory(factoryFunction), - this._constructDependencies(dependencies), + this._constructDependencies(factoryFunction, dependencies), true ); } - _constructDependencies(deps:List) { - return ListWrapper.map(deps, (t) => new Dependency(Key.get(t), false, false)); + _constructDependencies(factoryFunction:Function, dependencies:List) { + return isBlank(dependencies) ? + reflector.dependencies(factoryFunction) : + ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false)); } } \ No newline at end of file diff --git a/modules/di/src/exceptions.js b/modules/di/src/exceptions.js index 1735ed2a0d..1cbba5a917 100644 --- a/modules/di/src/exceptions.js +++ b/modules/di/src/exceptions.js @@ -77,8 +77,8 @@ export class InvalidBindingError extends Error { } export class NoAnnotationError extends Error { - constructor(type) { - this.message = `Cannot resolve all parameters for ${stringify(type)}`; + constructor(typeOrFunc) { + this.message = `Cannot resolve all parameters for ${stringify(typeOrFunc)}`; } toString() { diff --git a/modules/di/src/reflector.dart b/modules/di/src/reflector.dart index 89ec160371..db18c99c7c 100644 --- a/modules/di/src/reflector.dart +++ b/modules/di/src/reflector.dart @@ -23,12 +23,13 @@ class Reflector { return (args) => create(name, args).reflectee; } - List dependencies(Type type) { - ClassMirror classMirror = reflectType(type); - MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + List dependencies(typeOrFunc) { + final parameters = typeOrFunc is Type ? + _constructorParameters(typeOrFunc) : + _functionParameters(typeOrFunc); - return new List.generate(ctor.parameters.length, (int pos) { - ParameterMirror p = ctor.parameters[pos]; + return new List.generate(parameters.length, (int pos) { + ParameterMirror p = parameters[pos]; final metadata = p.metadata.map((m) => m.reflectee); @@ -49,10 +50,20 @@ class Reflector { return new Dependency(Key.get(p.type.reflectedType), false, false); } else { - throw new NoAnnotationError(type); + throw new NoAnnotationError(typeOrFunc); } }, growable:false); } + + List _functionParameters(Function func) { + return reflect(func).function.parameters; + } + + List _constructorParameters(Type type) { + ClassMirror classMirror = reflectType(type); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + return ctor.parameters; + } } final Reflector reflector = new Reflector(); diff --git a/modules/di/src/reflector.es6 b/modules/di/src/reflector.es6 index ae09f7cbfe..ddcb406665 100644 --- a/modules/di/src/reflector.es6 +++ b/modules/di/src/reflector.es6 @@ -14,14 +14,14 @@ class Reflector { return (args) => factoryFunction(...args); } - dependencies(type:Type):List { - var p = type.parameters; - if (p == undefined && type.length == 0) return []; - if (p == undefined) throw new NoAnnotationError(type); - return type.parameters.map((p) => this._extractToken(type, p)); + dependencies(typeOrFunc):List { + var p = typeOrFunc.parameters; + if (p == undefined && typeOrFunc.length == 0) return []; + if (p == undefined) throw new NoAnnotationError(typeOrFunc); + return typeOrFunc.parameters.map((p) => this._extractToken(typeOrFunc, p)); } - _extractToken(constructedType:Type, annotations) { + _extractToken(typeOrFunc, annotations) { var type; for (var paramAnnotation of annotations) { @@ -42,7 +42,7 @@ class Reflector { if (isPresent(type)) { return this._createDependency(type, false, false); } else { - throw new NoAnnotationError(constructedType); + throw new NoAnnotationError(typeOrFunc); } } diff --git a/modules/di/test/di/async_spec.js b/modules/di/test/di/async_spec.js index d23993de5f..f4248fa881 100644 --- a/modules/di/test/di/async_spec.js +++ b/modules/di/test/di/async_spec.js @@ -30,7 +30,7 @@ export function main() { describe("asyncGet", function () { it('should return a future', function () { var injector = new Injector([ - bind(UserList).toAsyncFactory([], fetchUsers) + bind(UserList).toAsyncFactory(fetchUsers) ]); var p = injector.asyncGet(UserList); expect(p).toBeFuture(); @@ -64,7 +64,7 @@ export function main() { it('should return a future when instantiating a sync binding ' + 'with an async dependency', function (done) { var injector = new Injector([ - bind(UserList).toAsyncFactory([], fetchUsers), + bind(UserList).toAsyncFactory(fetchUsers), UserController ]); @@ -77,7 +77,7 @@ export function main() { it("should create only one instance (async + async)", function (done) { var injector = new Injector([ - bind(UserList).toAsyncFactory([], fetchUsers) + bind(UserList).toAsyncFactory(fetchUsers) ]); var ul1 = injector.asyncGet(UserList); @@ -109,7 +109,7 @@ export function main() { it('should show the full path when error happens in a constructor', function (done) { var injector = new Injector([ UserController, - bind(UserList).toAsyncFactory([], function () { + bind(UserList).toAsyncFactory(function () { throw "Broken UserList"; }) ]); @@ -125,7 +125,7 @@ export function main() { describe("get", function () { it('should throw when instantiating an async binding', function () { var injector = new Injector([ - bind(UserList).toAsyncFactory([], fetchUsers) + bind(UserList).toAsyncFactory(fetchUsers) ]); expect(() => injector.get(UserList)) @@ -134,7 +134,7 @@ export function main() { it('should throw when instantiating a sync binding with an dependency', function () { var injector = new Injector([ - bind(UserList).toAsyncFactory([], fetchUsers), + bind(UserList).toAsyncFactory(fetchUsers), UserController ]); @@ -144,7 +144,7 @@ export function main() { it('should resolve synchronously when an async dependency requested as a future', function () { var injector = new Injector([ - bind(UserList).toAsyncFactory([], fetchUsers), + bind(UserList).toAsyncFactory(fetchUsers), AsyncUserController ]); var controller = injector.get(AsyncUserController); @@ -155,7 +155,7 @@ export function main() { it('should wrap sync dependencies into futures if required', function () { var injector = new Injector([ - bind(UserList).toFactory([], () => new UserList()), + bind(UserList).toFactory(() => new UserList()), AsyncUserController ]); var controller = injector.get(AsyncUserController); diff --git a/modules/di/test/di/injector_spec.js b/modules/di/test/di/injector_spec.js index d46dfe706a..72a92c4b9d 100644 --- a/modules/di/test/di/injector_spec.js +++ b/modules/di/test/di/injector_spec.js @@ -108,9 +108,24 @@ export function main() { }); it('should bind to a factory', function () { + function sportsCarFactory(e:Engine) { + return new SportsCar(e); + } + var injector = new Injector([ Engine, - bind(Car).toFactory([Engine], (e) => new SportsCar(e)) + bind(Car).toFactory(sportsCarFactory) + ]); + + var car = injector.get(Car); + expect(car).toBeAnInstanceOf(SportsCar); + expect(car.engine).toBeAnInstanceOf(Engine); + }); + + it('should support overriding factory dependencies', function () { + var injector = new Injector([ + Engine, + bind(Car).toFactory((e) => new SportsCar(e), {dependencies: [Engine]}) ]); var car = injector.get(Car); @@ -181,7 +196,7 @@ export function main() { var injector = new Injector([ Car, - bind(Engine).toFactory([], () => isBroken ? new BrokenEngine() : new Engine()) + bind(Engine).toFactory(() => isBroken ? new BrokenEngine() : new Engine()) ]); expect(() => injector.get(Car)).toThrow();