refactor(injector): change toFactory to use reflector to construct dependencies

This commit is contained in:
vsavkin 2014-10-09 11:35:13 -04:00
parent 06a221671c
commit d313cac42f
6 changed files with 61 additions and 32 deletions

View File

@ -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 {List, MapWrapper, ListWrapper} from 'facade/collection';
import {reflector} from './reflector'; import {reflector} from './reflector';
import {Key} from './key'; import {Key} from './key';
@ -13,6 +13,7 @@ export class Dependency {
this.lazy = lazy; this.lazy = lazy;
} }
} }
export class Binding { export class Binding {
constructor(key:Key, factory:Function, dependencies:List, providedAsFuture:bool) { constructor(key:Key, factory:Function, dependencies:List, providedAsFuture:bool) {
this.key = key; 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( return new Binding(
Key.get(this.token), Key.get(this.token),
reflector.convertToFactory(factoryFunction), reflector.convertToFactory(factoryFunction),
this._constructDependencies(dependencies), this._constructDependencies(factoryFunction, dependencies),
false false
); );
} }
toAsyncFactory(dependencies:List, factoryFunction:Function):Binding { toAsyncFactory(factoryFunction:Function, {dependencies=null}={}):Binding {
return new Binding( return new Binding(
Key.get(this.token), Key.get(this.token),
reflector.convertToFactory(factoryFunction), reflector.convertToFactory(factoryFunction),
this._constructDependencies(dependencies), this._constructDependencies(factoryFunction, dependencies),
true true
); );
} }
_constructDependencies(deps:List) { _constructDependencies(factoryFunction:Function, dependencies:List) {
return ListWrapper.map(deps, (t) => new Dependency(Key.get(t), false, false)); return isBlank(dependencies) ?
reflector.dependencies(factoryFunction) :
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false));
} }
} }

View File

@ -77,8 +77,8 @@ export class InvalidBindingError extends Error {
} }
export class NoAnnotationError extends Error { export class NoAnnotationError extends Error {
constructor(type) { constructor(typeOrFunc) {
this.message = `Cannot resolve all parameters for ${stringify(type)}`; this.message = `Cannot resolve all parameters for ${stringify(typeOrFunc)}`;
} }
toString() { toString() {

View File

@ -23,12 +23,13 @@ class Reflector {
return (args) => create(name, args).reflectee; return (args) => create(name, args).reflectee;
} }
List<Dependency> dependencies(Type type) { List<Dependency> dependencies(typeOrFunc) {
ClassMirror classMirror = reflectType(type); final parameters = typeOrFunc is Type ?
MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; _constructorParameters(typeOrFunc) :
_functionParameters(typeOrFunc);
return new List.generate(ctor.parameters.length, (int pos) { return new List.generate(parameters.length, (int pos) {
ParameterMirror p = ctor.parameters[pos]; ParameterMirror p = parameters[pos];
final metadata = p.metadata.map((m) => m.reflectee); final metadata = p.metadata.map((m) => m.reflectee);
@ -49,10 +50,20 @@ class Reflector {
return new Dependency(Key.get(p.type.reflectedType), false, false); return new Dependency(Key.get(p.type.reflectedType), false, false);
} else { } else {
throw new NoAnnotationError(type); throw new NoAnnotationError(typeOrFunc);
} }
}, growable:false); }, growable:false);
} }
List<ParameterMirror> _functionParameters(Function func) {
return reflect(func).function.parameters;
}
List<ParameterMirror> _constructorParameters(Type type) {
ClassMirror classMirror = reflectType(type);
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
return ctor.parameters;
}
} }
final Reflector reflector = new Reflector(); final Reflector reflector = new Reflector();

View File

@ -14,14 +14,14 @@ class Reflector {
return (args) => factoryFunction(...args); return (args) => factoryFunction(...args);
} }
dependencies(type:Type):List { dependencies(typeOrFunc):List {
var p = type.parameters; var p = typeOrFunc.parameters;
if (p == undefined && type.length == 0) return []; if (p == undefined && typeOrFunc.length == 0) return [];
if (p == undefined) throw new NoAnnotationError(type); if (p == undefined) throw new NoAnnotationError(typeOrFunc);
return type.parameters.map((p) => this._extractToken(type, p)); return typeOrFunc.parameters.map((p) => this._extractToken(typeOrFunc, p));
} }
_extractToken(constructedType:Type, annotations) { _extractToken(typeOrFunc, annotations) {
var type; var type;
for (var paramAnnotation of annotations) { for (var paramAnnotation of annotations) {
@ -42,7 +42,7 @@ class Reflector {
if (isPresent(type)) { if (isPresent(type)) {
return this._createDependency(type, false, false); return this._createDependency(type, false, false);
} else { } else {
throw new NoAnnotationError(constructedType); throw new NoAnnotationError(typeOrFunc);
} }
} }

View File

@ -30,7 +30,7 @@ export function main() {
describe("asyncGet", function () { describe("asyncGet", function () {
it('should return a future', function () { it('should return a future', function () {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toAsyncFactory([], fetchUsers) bind(UserList).toAsyncFactory(fetchUsers)
]); ]);
var p = injector.asyncGet(UserList); var p = injector.asyncGet(UserList);
expect(p).toBeFuture(); expect(p).toBeFuture();
@ -64,7 +64,7 @@ export function main() {
it('should return a future when instantiating a sync binding ' + it('should return a future when instantiating a sync binding ' +
'with an async dependency', function (done) { 'with an async dependency', function (done) {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toAsyncFactory([], fetchUsers), bind(UserList).toAsyncFactory(fetchUsers),
UserController UserController
]); ]);
@ -77,7 +77,7 @@ export function main() {
it("should create only one instance (async + async)", function (done) { it("should create only one instance (async + async)", function (done) {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toAsyncFactory([], fetchUsers) bind(UserList).toAsyncFactory(fetchUsers)
]); ]);
var ul1 = injector.asyncGet(UserList); 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) { it('should show the full path when error happens in a constructor', function (done) {
var injector = new Injector([ var injector = new Injector([
UserController, UserController,
bind(UserList).toAsyncFactory([], function () { bind(UserList).toAsyncFactory(function () {
throw "Broken UserList"; throw "Broken UserList";
}) })
]); ]);
@ -125,7 +125,7 @@ export function main() {
describe("get", function () { describe("get", function () {
it('should throw when instantiating an async binding', function () { it('should throw when instantiating an async binding', function () {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toAsyncFactory([], fetchUsers) bind(UserList).toAsyncFactory(fetchUsers)
]); ]);
expect(() => injector.get(UserList)) expect(() => injector.get(UserList))
@ -134,7 +134,7 @@ export function main() {
it('should throw when instantiating a sync binding with an dependency', function () { it('should throw when instantiating a sync binding with an dependency', function () {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toAsyncFactory([], fetchUsers), bind(UserList).toAsyncFactory(fetchUsers),
UserController UserController
]); ]);
@ -144,7 +144,7 @@ export function main() {
it('should resolve synchronously when an async dependency requested as a future', function () { it('should resolve synchronously when an async dependency requested as a future', function () {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toAsyncFactory([], fetchUsers), bind(UserList).toAsyncFactory(fetchUsers),
AsyncUserController AsyncUserController
]); ]);
var controller = injector.get(AsyncUserController); var controller = injector.get(AsyncUserController);
@ -155,7 +155,7 @@ export function main() {
it('should wrap sync dependencies into futures if required', function () { it('should wrap sync dependencies into futures if required', function () {
var injector = new Injector([ var injector = new Injector([
bind(UserList).toFactory([], () => new UserList()), bind(UserList).toFactory(() => new UserList()),
AsyncUserController AsyncUserController
]); ]);
var controller = injector.get(AsyncUserController); var controller = injector.get(AsyncUserController);

View File

@ -108,9 +108,24 @@ export function main() {
}); });
it('should bind to a factory', function () { it('should bind to a factory', function () {
function sportsCarFactory(e:Engine) {
return new SportsCar(e);
}
var injector = new Injector([ var injector = new Injector([
Engine, 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); var car = injector.get(Car);
@ -181,7 +196,7 @@ export function main() {
var injector = new Injector([ var injector = new Injector([
Car, Car,
bind(Engine).toFactory([], () => isBroken ? new BrokenEngine() : new Engine()) bind(Engine).toFactory(() => isBroken ? new BrokenEngine() : new Engine())
]); ]);
expect(() => injector.get(Car)).toThrow(); expect(() => injector.get(Car)).toThrow();