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
Leave a Reply
Apr 30th 2010 • 04:04
by Glidias
This was what I had somewhat had with ‘subject-based’ bindings used in a guerilla-based NodeClassSpawnerManager serialization utility I made (Website link above) . One of the problems with subject-based mappings though, is the use of method parameter string-based lookups to momentarily expose certain configurations at the time of execution, which veers more towrads JIT service-locator mapping. In fact, i had to resort to having 3 layers, 1) additionalBindingToResolveStringValue:Function (if available), 2) provided_base_subject_key:String/AnyKeyTypeForDictionary (if available), 3) and the_resolved_fullyQualfiedClassName, to create mappings specific to a given class and situation when wanting to spawn/inject-into instances from string-based XML nodes/attributes.
I noticed that in the private configurations, you exposed the concrete Classes within them. Albeit this approach provides compile-time strict-typing, it can add to the file-size imports if your configuration is done elsewhere in another application domain (where a string-based approach might be necessary to avoid re-importing code-bases). Could generic catch-all interface signatures (non-concrete-class) mappings be exposed in private configurations? I think detection might not work for interface-based keys, since you’d have to manually inspect classes for exposed interface signatures which is additional overhead for the injector.
May 4th 2010 • 08:05
by sammy
Thanks for pointing me to your project, I will take a look. Interesting thoughts about using separate application domains. I am working on a demo of using dawn in separate application domains atm which should help raise any issues
sammy
May 3rd 2010 • 04:05
by Jon Toland
Did you see Till’s child injection solution? It creates instance contexts in lieu of keyed groups. I still plan on writing a piece about Dawn and RL and ideal application architecture just been buried on this contract. Joel has been talking about RL 2 and depending how much flexibility there is I’d love to see your hands in it! Dawn’s looking sharp
May 4th 2010 • 08:05
by sammy
Hey Jon, yes I have seen Till’s child injectors, excellent stuff!! Private configurations actually are child injectors under the hood, the installPrivate method returns the child injector created for that private configuration should you want it. And you can also just create a child injector through the createChildInjector method on IInjector. Take a peek at the new IInjector api
Though I would advise using private configurations wherever possible. Manually creating child injectors looks more suited to dealing with modules than separating object graphs (in dawn anyhow), more on that in another post
Always interested to hear whats coming in RL! Its an awesome framework.
Cheers
Sammy
EDIT: Corrected all my spelling mistakes, I hope
May 7th 2010 • 09:05
by Tom Kordys
I see more documentation
Nice