Archive → April, 2010
Similar, but different! Private Configurations
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)
class Car {
var engine:Engine;
var transmission:Transmission;
var driveLine:IDriveLine;
public function Car(engine:Engine, transmission:Transmission, driveLine:IDriveLine) {
// set properties etc //
}
}
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.
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());
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
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");
}
}
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:
var injector:IInjector = Injector.createInjector(); injector.installPrivate(new ChelseaTractorConfig()); injector.installPrivate(new SportyCarConfig());
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.
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());
Now if we request a Car from the injector (injector.inject(Car)) we will get the default options:
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
To activate a private configuration we must request one of the exposed mappings. Ok, lets create a sporty car.
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
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
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
This time we requested a mapping of type Car named 4×4, 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
dawn injectors multi-mappings
I just added a new feature to the dev branch of dawn, and since I haven’t got changelog files yet (bad sammy!!) I figured I would throw a post up so it doesn’t get lost. Seriously though, there will be a changelog
This feature will make the next version of dawn, unless someone convinces me its bonkers.
I am aiming for dawns injector to be a complete injection solution (shiny bells AND whistles!!). Its not doing bad, but there are a few places that some polish wouldn’t hurt (better error reporting is a big on one those, its on the way).
This latest change was born out of a request from Mr. Tink. He wanted to be able to map one class (in singleton scope) to two interfaces. While such an exciting feat was possible previously, it really required knowledge of factory providers (and consequently some custom code). The new solution is much neater and is as simple as pie… I hope, O.K. an example:
Lets say I have a model object that implements a simple interface for views (or mediators or whatever said framework fancies). Simple, except that I also want methods on the object that shouldn’t be exposed to the view objects, so those can be added to an interface that extends the original one with the extra functionality.
// for dumb clients things like views etc
interface IAccessData {
function getMyThing():Thing;
}
// for services/commands etc
interface IChangeData extends IAccessData {
function updateMyThing(wibble:Wibble):void;
}
class ThingModel implements IChangeData { ... }
ThingModel implements both of these interfaces, and I only want one of my models to be created and that same instance to be injected for anything that require either IChangeData or IAccessData.
Previously I would have either had to map to a factory or an instance twice to get this to work, i.e.
// pray it has no constructor dependencies var myModel:ThingModel = new ThingModel(); injector.map(IAccessData).toInstance(myModel); injector.map(IChangeData).toInstance(myModel);
or
injector.map(IAccessData).toFactory(ThingModelFactory);
injector.map(IChangeData).toFactory(ThingModelFactory);
class ThingModelFactory { ... blah ... }
Either way, I have to create two mappings and its not so fun. If there was a third interface, or I wanted to also map the concrete class to the same instance it would require a third mapping.
In the next version of dawn (already in the dev branch) we can just do this
injector.map(IAccessData).and(IChangeData).to(ThingModel).asSingleton();
Ta Da!!
Much easier. Under the hood dawn still creates two mappings, its just sugar coated with the mapping DSL and our lovely ‘and’
Its also “chainable” (is that a word? I doubt it).. so if I also wanted classes that depend on the concrete implementation of ThingModel to get the same instance, I could just add another ‘and’
injector.map(IAccessData).and(IChangeData).and(ThingModel).to(ThingModel).asSingleton();
And there you have it, multi-mapping goodness. There are lots of other things coming soon, I just needed to get that one off my chest.
Also worth checking out is the asEagerSinglton scope that just made it into 0.8