-
Notifications
You must be signed in to change notification settings - Fork 4
DawnInjections
It’s time to go into some more detail about how to make Dawn inject the classes we want. Below we’ll run through the metadata Dawn needs sprinkled across those classes as well as the ActionScript configuration (where necessary) to make it all come together.
There’s a few different ways to inject classes, which needs a bit of explaining, so here we go:
Lets talk about that car again. The car depends directly on a LittleEngine
, we (and Dawn) can see since the [Inject]
metadata has been added to the engine
variable:
class Car { [Inject] public var engine:LittleEngine; public function Car(){} public function startCar():void { engine.start(); } }
Dawn needs no configuration at all to create that Car
for us! That’s because Dawn can make sensible decisions where you don’t tell it otherwise, so when we ask Dawn to create our Car
it will just create a Car
object, and a LittleEngine
object, wire them up and we’re away!
Creating the Car
with Dawn would look something like this:
// create a Dawn injector var injector:IInjector = Injector.createInjector(); // ask the injector to create and inject a Car for us var car:Car = Car(injector.inject(Car))
That’s a pretty simplistic example (fairly limited wow factor?), so let’s go for something a little more concrete: a concrete implementation… What if we wanted a Car
that was more flexible? We could, for example, create an IEngine
interface and rework our car to use one of those.
class Car { private var engine:IEngine; public function Car(engine:IEngine){ this.engine = engine; } public function startCar():void { engine.start(); } }
Now our Car
could work with any engine that implements the IEngine
interface, and we have the power via Dawn configuration to swap out the engine for something else.
Also worth noting is that the engine instance is now a constructor argument, that’s because I had a think about it, and a Car
without an engine isn’t much use! (Dawn is fine with constructor dependencies, so it’s no bother)
So… if we still wanted the LittleEngine
the configuration would look like this injector.map(IEngine).to(LittleEngine);
, but wait, maybe we want to up-size: injector.map(IEngine).to(MassiveEngine);
. Depending on which of those lines we use when Dawn creates our Car
it will also create and inject the mapped implementation of IEngine
We don’t need to create an external configuration for something this simple, so I’ll just use the handy map
function on the injector
// create a Dawn injector var injector:IInjector = Injector.createInjector(); // lets still have a little engine (think of the children... or something) injector.map(IEngine).to(LittleEngine); // ask the injector to create and inject a @Car@ for us var car:Car = Car(injector.inject(Car)) // Ta Da!!
Well that’s the basics over with, good job!
Now we’ve covered the basics of injections we can move on to some more contrived automobile examples! Lets extend out Car
a couple of times, and create a SportsCar
and a HybridCar
. They are going to want different types of engines, and why not some different types of interior.
First we can add the interior property to our Car
, I’ll use a method to inject it this time, just to mix it up:
class Car { private var interior:IInterior; private var engine:IEngine; public function Car(engine:IEngine){ this.engine = engine; } [Inject] public function setInterior(interior:IInterior):void{ this.interior = interior; } public function startCar():void { engine.start(); } }
Now to create the two subclasses. Both SportsCar
and HybridCar
will have very different engines, but that is easy enough for us to configure, we can simply type their constructor’s engine parameter to the type of engine they use eg function SportsCar(engine:SportyEngine)
.
How about the interior? The setInterior method is typed to IInterior
, and there is no way to override and change that (to a concrete type, or sub-interface) in SportsCar
or HybridCar
, but we do want them to have different interiors. This is where named injections come in! We can override the setInterior method and set unique names for the instances the two types of cars require. Take a look at these class definitions:
class SportsCar extends Car { public function SportsCar(engine:SportyEngine){ super(engine) } [Inject] [Named(index="1", name="sporty")] override public function setInterior(interior:IInterior):void{ super.setInterior(interior); } } class HybridCar extends Car { public function HybridCar(engine:HybridEngine){ super(engine); } [Inject] [Named(index="1", name="hybrid")] override public function setInterior(interior:IInterior):void{ super.setInterior(interior); } }
You can see that in the overrides for setInterior in both the sub classes I have added the Named
metadata. Named
allows us to explain to Dawn via metadata that there is a particular mapping that we are after, and it can be distinguished by name.
In the case of the SportsCar
we can see that it would like to be injected with the sporty
IInterior
mapping. Lets take a look at the configuration for these classes and see if that makes it all clear.
injector.map(IInterior).to(BasicInterior); injector.map(IInterior).to(SportyInterior).named("sporty"); injector.map(IInterior).to(HybridInterior).named("hybrid");
With the above configuration we have mapped IInterior
three times. The first line maps the IInterior
interface to the BasicInterior
class, so that any class that wants a IInterior
and does not specify a name will get a BasicInterior
. The next two lines again map the IInterior
interface to two concrete classes, only in these cases the mappings are given names.
In the above scenario when Dawn creates a HybridCar
it will look for an IInterior
mapping with the name hybrid
, and it will find the HybridInterior
class.
And there you have it, the named feature allows us to chose the implementation we want for a given class. WIN
In all the examples above Dawn has created the objects as well as injecting them with their dependencies. Sometimes however you already have the instance of an object. When this happens (don’t fear!!) you can use the toInstance mapping Dawn provides
injector.map(AbstractService).named("customer service").toInstance(custService); // ... in mxml <mx:RemoteObject id="custService" ... />
It is not always possible for Dawn to construct everything you need, perhaps you are relying on some third party code, or you have some logic you want to perform before creating a instance of a class. This is when factory providers come in handy, they allow you to write a custom class that performs the instantiation of your object.
To demonstrate we might as well pretend our car needs a GPS device, a third party GPS device of course. The GpsSystem
class depends on having a CountryMap
instance of the locale we’re in. To provide each Car
with a new GpsSystem
we will use a factory provider
Our upgraded Car
now looks like this
class Car { protected var engine:IEngine; protected var _gps:GpsSystem; public function Car(engine:IEngine){ this.engine = engine; } [Inject] public function set gps(value:GpsSystem):void{ _gps = value; } }
Now we need some custom code to create the GpsSystem
for the Car
class GpsFactory{ [Inject(name="country code")] public var locale:String public function GpsFactory(){} [Provider] public function getNewGps():GpsSystem{ var map:CountryMap = new CountryMap( locale ); var gps:GpsSystem = new GpsSystem( map ); return gps; } }
Now we can map the GpsSystem
to our factory so Dawn knows to use it to create instances.
mapper.map(GpsSystem).toFactory(GpsFactory);
Thats all there is to it. The gps
property of the Car
will be populated by Dawn using the custom factory we wrote above. That means that whenever Dawn sees that a class has a dependency on GpsSystem
it will call the getNewGps
function within its GpsFactory
instance. It knows to call the getNewGps
function as that is the one with the [Provider]
metadata.
The great thing about Dawn factory providers is that your code need never depend on them. Dawn creates the GpsFactory
and calls its provider method, so all your code needs to do it require a GpsSystem
.
You might also have noticed that the GpsFactory
has a dependency of its own on a String
with a name “country code”. Factories can have dependencies just like any other Dawn managed object. More on injecting @String@s and other properties further down the page in injecting properties
A glorious benefit of Dawn and dependency injection is that you no longer need to use the dreaded singleton pattern (or any nasty static state come to think of it). Single scope is not the singleton pattern in anther guise, it’s just a way of explaining to Dawn how and when you want to create new instances of classes.
There are two built-in scopes in Dawn: Singleton and Transient, and the difference is simple but very important. A class that is scoped as a singleton will only be instantiated once by Dawn (within the life time of an IInjector
). Otherwise a new instance of a class is created every time Dawn need to inject it.
Here’s an example: lets say we have two classes that require one object… a Flower
and a Tree
, both require a Sun
… you see?
class Flower { public var sun:Sun; public function Flower(sun:Sun){ this.sun = sun; } } class Tree { public var sun:Sun; public function Tree(sun:Sun){ this.sun = sun; } } class Sun{}
If we were to create a Flower
and a Tree
using Dawn with no configuration both will get different instances of Sun
… that sounds worrying! So we need to tell Dawn that there really is only ONE Sun
, and both trees and flowers should get the same instance of Sun
.
We can do that with one little line:
injector.map(Sun).asSingleton();
And nature is as it should be.
One common requirement of a project is that a model or service object should be create at startup time even though the views that use (depend) on it are not created until some later date. To force creation of such objects we can use the asEagerSingleton
scope. Such objects will be created as soon as their configuration in installed, or if the map
function on the injector is used, then as soon as the first object in injected.
Dawn makes it nice and easy to remove hardcoded properties from your classes. Ever got in a pickle when you find the url to the development server in the live swf… eeeek! With Dawn you INJECT… I expect you’ve got the message by now.. so what does injecting these properties look like? Glad you asked…
class RpcService { [Inject(name="rpc url")] public var url:String; } injector.map(String).named("rpc url").toInstance("http://www.wibble.com/service/messagebroker/")
Now any class that wants the rpc url can just request it via the [Inject]
metadata, no more hardcoding!! WIN
Lots of objects need to do something when they are created, add some listeners, set some properties, you know what it’s like. But how does an object know when it has had all its dependencies injected? Well Dawn will let it know, if it asks of course.
class RpcService { [Inject(name="rpc url")] public var url:String; [PostConstruct] public function loginAtStartup():void{ // do things.. ummmm } }
Just add the PostConstruct
metadata and Dawn will call the annotated function when it has finished creating it, so in the above example the loginAtStartup
function will be called after the rpc url has been set on that object.