Skip to content
This repository has been archived by the owner on Feb 6, 2020. It is now read-only.

DawnInjections

sammyt edited this page Sep 13, 2010 · 41 revisions

More Details on Injecting Stuff

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:

Basic Injections

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.

Named Injections

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.

Factory Driven Injections

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

singleton scope

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.

injecting properties

optional injections

completion trigger

Clone this wiki locally