Dependency injection examples in AS3 by Drew Bourne March 26th, 2007
After reading Theo Hultbergs posts about dependency injection in Flash and Flex I though it appropriate to post some examples of how I have been using DI here in our Flash applications.
I have written a custom framework to handle Event-to-Command delegation (very loosely based on the Cairngorm framework) which requires Events and Commands to be registered with a Front Controller.
To register the events and commands I subclass my DI Front Controller implementation and with a simple loop and Array of Arrays I can register my commands and any parameters I want passed to their constructors.
Examples after the cut
package example.di
{
import com.anywebcam.framework.controller.DIFrontController;
import example.di.command.*;
import example.di.event.*;
public class ExampleFrontController extends DIFrontController
{
private var _commands:Array =
[
/* [ event:String, command:Class, parameters:Array=null ] */
[ ApplicationEvent.START, ApplicationStartCommand ],
[ ApplicationErrorEvent.ERROR, ApplicationErrorCommand ],
[ UserEvent.AUTHENTICATE, UserAuthenticateCommand,
[{config: Configuration},
{userDelegate: UserDelegate}] ],
[ ModelEvent.POPULATE, ModelPopulateCommand,
[{model: ModelLocator},
{config: Configuration},
{flashvars: 'config.flashvars'}] ]
];
public function ExampleFrontController( container:DIContainer )
{
super( container );
_commands.forEach( function( element:Array, index:int, array:Array ):void
{
var event:String = element[0] as String;
var command:Class = element[1] as Class;
var params:Array = element.length > 2 ? element[2] as Array : null;
addCommand( event, command, params );
} );
}
}
}
It is worth noting that in the parameters I am not passing instances of objects but instead the Class of the object which I would like to be injected into the Command instance. The String value of ‘config.flashvars’ will actually be used as a look-up key as demonstrated later.
The DIFrontController uses the DIContainer that was passed to its constructor to search for implementations of the parameter types, creates instances of them and injects them into the constructor of the Command classes.
Creating the DIContainer and registering types-to-implementations is done in a similar fashion to Events & Commands. We also add support for using setter or constructor injection of parameters, and the ability to cache instances if we want to.
package example.di
{
import com.anywebcam.dependencyinjection.component.*;
import com.anywebcam.dependencyinjection.container.*;
import mx.containers.Canvas;
public class ExampleDIApplication extends Canvas
{
private var _dependencyConfig:Object =
{
defaults:
{
cache: true,
injectionType: 'setter' // or 'constructor'
},
dependencies:
[
/* [ key:*, type:Class, params:Array=null, options:Object=null ] */
[ Configuration, E4XConfiguration, [{data: _config}],
// this instance is used in the ModelPopulateCommand above
[ 'config.flashvars', FlashVarsConfiguration, [{stage: Stage}], {injectionType: ‘constructor’} ],
[ ModelLocator, DynamicBindableModelLocator ],
[ 'service.user.id', HTTPService, [{url:'http://...', literal:true}] ],
[ 'service.user.auth', HTTPService, [{url:'http://...', literal:true}] ],
[ UserDelegate, UserDelegate,
[{userIdService: 'service.user.id'},
{userAuthService: 'service.user.auth'}] ]
]
}
public var container:DIContainer;
public function ExampleDIApplication()
{
super();
container = new DefaultDIContainer();
// setup DI defaults
var defaults:Object;
if( !defaults.hasOwnProperty(’cache’) ){ defaults.cache = false; }
if( !defaults.hasOwnProperty(’injectionType’) ){ defaults.injectionType = ’setter’; }
_dependencyConfig.dependencies.forEach(
function( element:Array, index:int, array:Array ):void
{
// determine if we cache instances
var cache:Boolean
= (element.length == 4 && element[3].hasOwnProperty(’cache’))
? element[3].cache
: defaults.cache;
// determine the dependency injection method
var injectionType:String
= (element.length == 4 && element[3].hasOwnProperty(’injectionType’))
? element[3].injectionType
: defaults.injectionType;
var adapter:DIComponentAdapter;
// determine if this instance has additional parameters
var params:Array
= element.length > 2
? element[2]
: null;
// get our DIComponentAdapter
switch( injectionType )
{
case ’setter’:
{
adapter = new SetterInjectionComponentAdapter( element[0], element[1], params );
break;
}
case ‘constructor’:
{
adapter = new ConstructorInjectionComponentAdapter( element[0], element[1], params );
break;
}
}
// Wrap it in a DIComponentAdapter that will cache our instance if we need to
if( cache )
{
adapter = new CachingComponentAdapter( adapter );
}
// register the instance with the container
container.register( adapter );
});
}
}
}
By using dependency injection in this fashion I have found that my Command classes are smaller and more atomic as I can focus on performing a specific unit of work rather than the house-keeping of creating instances of services, delegates, and events. Some other advantages include lazy-initialization of dependencies (ie: only creating them when they are needed), easier use of Mock / Stub objects (for unit- & integration- testing), easier replacement of implementations (because we code to interfaces).
The DI framework used in these examples is heavily inspired by PicoContainer for Java with changes appropriate for AS3 and the Flash reflection support through describeType. More about our DI and application framework will be forthcoming shortly.






April 2nd, 2007 at 9:28 pm
Hi there - really interesting post. I’m looking at how to apply test-driven patterns from the Java world in Flex. So I was wondering, what do you use for mocking dependencies in your unit tests?
April 3rd, 2007 at 11:13 am
At the moment I am simply creating basic implementations of the interfaces i need to mock. I use flag variables internally to check that methods have been called with the expected parameters and return values. Its not a great solution, and it makes writing tests awkward.
I have looked at using the Flash Proxy object to make creating Mocks with expectations and return values a simpler process that can be setup inline with the tests, however it will still require an implementation of the interface (as Flash requires all classes, even dynamic ones, to explicitly define the methods from any interface they implement). eg:
Example interface:
package com.anywebcam.example { public interface MyInterface { function myMethod():void; } }Mocked implementation:
package test { dynamic public class MyInterfaceMock extends Mock implements MyInterface { public function myMethod():void { // call the method on the superclass, allowing Mock to handle it. super.myMethod(); } } }Using the mock in a flexunit test case:
package test { public class AnObjectWithAMyInterfaceDependencyTest extends TestCase { public function testMyMethod():void { var mock:MyInterface = new MyInterfaceMock(); mock.shouldReceive('myMethod'); mock.shouldReceive('myOtherMethod') .with(1, 2, 3) .andReturn(6); var testee:AnObjectWithAMyInterfaceDependency = new AnObjectWithAMyInterfaceDependency( mock ); testee.doSomethingWithMyInterface(); mock.verify(); } } }At the moment this is just an example as I do not yet have an implementation for creating mocks this way.
PS: Comment updated with some additional code and explanation.