I've just added another feature to the development branch of dawn, called private configurations. So, what are they, and why did I bother?
When I first demoed Dawns injector in a LBi tech talk (almost a year ago now) one clever chap asked how it could be used to create a number of very similar, but different objects graphs. I only half got my head around the question at the time, and blurted out something about named injections. Then sometime (and some beers) later after discussing the problem more it became clear that Dawn didn't really have a very elegant solution to the problem.
Time to explain the problem (if you've heard of the robot leg problem, thats the one!). How can I create two similar object graphs, with some defined differences without having to create new concrete classes to represent those different graphs (thats what you would have todo today btw)? Example time.
Picture a Car class (or just read the one below)
Engine and Transmission are base classes that have a number of subclasses like PetrolEngine, DieselEngine, AutomaticTransmission and ManualTransmission. IDriveLine is an interface for the various type of drive the car could have, FrontWheel, RearWheel or FourWheel etc.
The Car class itself is pretty flexible (polymorphism is handy like that), depending on how we construct the car we could get a four wheel drive diesel or an manual transmission, rear wheel drive etc. In Dawn today you could create a configuration for your Car and install it into the injector.
Using the above code, every time you requested for a Car from the injector it would create one with a petrol engine, rear wheel drive and manual transmission. *Every Time* is not what we want though! Without private configurations we would have to find a way round this, most likely by creating subclasses of Car, such as PetrolCar, which had a constructor parameter of type PetrolEngine, then the same for RearWheelDriveCar, and so on, until we had classes to represent the varieties of object graphs we wanted.
Private configurations are a port of Guices solution (private modules) to the same problem. They allow you to create groups of mappings that are only available under certain conditions.
Here are a couple of private configurations for the above problem
If the two configurations above were just normal configurations we would have a little problem when we installed then into the injector, since they both create mappings for the same types. So the second configuration to be installed would overwrite the first ones mappings for Engine etc. Also notice that both the private configurations make one or more calls to an *expose* method.
Installing private configurations is almost identically to installing normal ones:
Installed private configurations do not overwrite mappings within other normal configurations or private configurations. So when we install the SportyCarConfig we have not overwritten the settings in the ChelseaTractorConfig. But how then do we create a car that uses either one of those configurations? Well thats what the expose method is all about! Private configurations are private (hidden) until a special exposed mapping is requested from the injector. Once the exposed mapping is requested the mappings within the private configuration become public, and take precedence over any configurations that already exist in the injector.
Lets expand the above code snippet a little to add some default mappings for the IDriveLine, Engine and Transmission.
Now if we request a Car from the injector (injector.inject(Car)) we will get the default options:
To activate a private configuration we must request one of the exposed mappings. Ok, lets create a sporty car.
This time when we asked the injector for a Car we specified the name of the mapping, Sporty. When the injector sees that we are requesting a mapping of type Car named Sporty, it activates the SportyCarConfig, since that is the *exposed* mapping of the configuration! It doesn't matter that the configuration does not supply a mapping for those options (type Car, named Sporty), it just knows that thats the mapping that unlocks it.
Now we can create a four wheel drive car
This time we requested a mapping of type Car named 4x4, which is one of the exposed mappings that activates the ChelseaTractorConfig, so the car we get is provisioned with the mappings found in the ChelseaTractorConfig over the default ones.
And that in a nutshell is what private configurations are all about. They are a terse and clean way to separate configurations for object graphs which can be activated by special exposed mappings.
This is all tested and committed to the dev branch of Dawn awaiting inclusion in the next release :)
class Car {
var engine:Engine;
var transmission:Transmission;
var driveLine:IDriveLine;
public function Car(engine:Engine, transmission:Transmission, driveLine:IDriveLine) {
// set properties etc //
}
}class CarConfig implements IConfiguration {
public function configure(mapper:IMapper):void {
mapper.map(Engine).to(PetrolEngine);
mapper.map(Transmission).to(ManualTransmission);
mapper.map(IDriveLine).to(RearWheel);
}
}
// then where you create your injector
injector.install(new CarConfig());class SportyCarConfig implements IPrivateConfiguration {
public function configure(mapper:IPrivateMapper):void {
mapper.map(Engine).to(PetrolEngine);
mapper.map(Transmission).to(ManualTransmission);
mapper.map(IDriveLine).to(RearWheel);
mapper.expose(Car, "Sporty");
}
}class ChelseaTractorConfig implements IPrivateConfiguration {
public function configure(mapper:IPrivateMapper):void {
mapper.map(Engine).to(DieselEngine);
mapper.map(Transmission).to(AutomaticTransmission);
mapper.map(IDriveLine).to(FourWheel);
mapper.expose(Car, "4x4");
mapper.expose(Showroom, "SUV Land");
}
}var injector:IInjector = Injector.createInjector(); injector.installPrivate(new ChelseaTractorConfig()); injector.installPrivate(new SportyCarConfig());
var injector:IInjector = Injector.createInjector(); // add default normal car mappings injector.map(IDriveLine).to(FrontWheel); injector.map(Engine).to(PetrolEngine); injector.map(Transmission).to(ManualTransmission); // install the private configurations injector.installPrivate(new ChelseaTractorConfig()); injector.installPrivate(new SportyCarConfig());
var car:Car = Car(injector.inject(Car)); trace(car.engine is PetrolEngine) // true trace(car.driveLine is FrontWheel) // true trace(car.transmission is ManualTransmission) // true
var sportyCar:Car = Car(injector.inject(Car, "Sporty")); trace(sportyCar.engine is PetrolEngine) // true trace(sportyCar.driveLine is RearWheel) // true trace(sportyCar.transmission is ManualTransmission) // true
var bigCar:Car = Car(injector.inject(Car, "4x4")); trace(bigCar.engine is DieselEngine) // true trace(bigCar.driveLine is FourWheel) // true trace(bigCar.transmission is AutomaticTransmission) // true