-
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 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(); } }
The configuration for the the car is going to be simple; we know we want a Car
object and a LittleEngine
object so we ask the mapper to map those classes to themselves.
public function create( mapper:IMapper ):void { mapper.map(LittleEngine).toSelf(); mapper.map(Car).toSelf(); }
In the above scenario Dawn knows that whenever it’s asked to create a Car
or a LittleEngine
it can instantiate them directly. In Dawn terminology the provider for Car
is Car
.
Thats a pretty simplistic example, 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 { [Inject] public var engine:IEngine; public function Car(){} 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 configuration to swap out the engine for something else. If we still wanted the LittleEngine
the configuration would look like this mapper.map(IEngine).toClass(LittleEngine);
, but maybe we want to up-size: mapper.map(IEngine).toClass(MassiveEngine);
. In the latter case, when Dawn sees the [Inject]
metadata on a IEngine
variable Dawn would construct a MassiveEngine
and inject that.
Now we’ve covered the basics of injections we can move on to some more contrived automobile examples! What about the scenario where we have more than one type of Car
, a SportsCar
and a HybridCar
. Both extend the Car
class and customize it in some way to make it more specific. How do we ensure they get the right type of engine?
Hang on, it’s time for a confession: we were a little lazy in defining the Car
class above. We just created a public variable to hold the engine instance, which is all well and good until we start thinking about extending classes and overriding functionality. So first things first, lets bring the Car
class up to scratch for proper OOP.
class Car { protected var _engine:IEngine; public function Car(){} public function startCar():void { engine.start(); } public function get engine():IEngine{ return _engine; } [Inject] public function set engine(value:IEngine):void{ _engine = value; } }
There we are, now we can create our SportsCar
and HybridCar
which override the default definition of engine. How’s that for fantabulous?:
class SportsCar extends Car { public function SportsCar(){} [Inject(name="sporty")] override public function set engine(value:IEngine):void{ _engine = value; } } class HybridCar extends Car { public function HybridCar(){} [Inject(name="hybrid")] override public function set engine(value:IEngine):void{ _engine = value; } }
By overriding the setter (sometimes it will be the getter – whichever has the metadata attached in the superclass) of Car
in the two subclasses we are able to add information to the inject metadata, in both cases we have added a name property.
This allows us to explain to Dawn via metadata that there is a particular provider that we are after, and it can be distinguished by name. If there’s ever alphabet rationing in a War On English, we’ll be ever so grateful that we decided, right here and now, to just refer to this as a Named Injection.
The configuration now needs a couple of extra lines, to explain the new mappings. When a dependency on IEngine
is seen, and it has a name of “hybrid”, Dawn should create a HybridEngine
, and similarly when it sees “sporty” it should create a SportsEngine
.
public function create( mapper:IMapper ):void { mapper.map(IEngine).toClass(LittleEngine); mapper.map(IEngine).toClass(HybridEngine).withName("hybrid"); mapper.map(IEngine).toClass(SportsEngine).withName("sporty"); mapper.map(Car).toSelf(); mapper.map(HybridCar).toSelf(); mapper.map(SportsCar).toSelf(); }
If Dawn were unable to find an IEngine with a given name (so when you haven’t created a named mapping) it would fall back to the nameless mapping.
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 some 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 some GPS, a third part GPS 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(){} public function startCar():void { engine.start(); } public function get gps():GpsSystem{ return _gps; } [Inject] public function set gps(value:GpsSystem):void{ _gps = value; } public function get engine():IEngine{ return _engine; } [Inject] public function set engine(value:IEngine):void{ _engine = 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; } }
Finally we need to map the dependency in the configuration by adding the last line
public function create( mapper:IMapper ):void { mapper.map(IEngine).toClass(LittleEngine); mapper.map(IEngine).toClass(HybridEngine).withName("hybrid"); mapper.map(IEngine).toClass(SportsEngine).withName("sporty"); mapper.map(Car).toSelf(); mapper.map(HybridCar).toSelf(); mapper.map(SportsCar).toSelf(); mapper.map(GpsSystem).toFactory(GpsFactory); }
Thats all there is to it. The gps
property of the Car
’s 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
can calls its provider method, 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 its not the singleton pattern in anther guise, its just a way of explaining to Dawn how and when you want to create new instances of classes. There are really only two scopes that you can give a class in Dawns configuration, singleton or well, not singleton 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 a builder object). Otherwise a new instance of a class is created every time Dawn need to inject it.