IoC/DI Has Little Point If You Don't Swap your Maps
This tip is sort of an add-on to the article here.
The whole point of Inversion of Control is so that you can pass different concrete implementations of an interface to a Controller that expects a class that implements a specific interface, but does not specify or even care what precise class it receives.
To specify which implementing class is used, you might write code such as this:
public class RepositoriesInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<iduckbilledplatypusrepository>().ImplementedBy
<duckbilledplatypusrepository>().LifestylePerWebRequest(),
Component.For<isiberiantigerrepository>().ImplementedBy
<siberiantigerrepository>().LifestylePerWebRequest(),
Component.For<ibottlenoseddolphinrepository>().ImplementedBy
<bottlenoseddolphinrepository>().LifestylePerWebRequest(),
Component.For<ipterodactylrepository>().ImplementedBy
<pterodactylrepository>().LifestylePerWebRequest(),
Component.For<ibutterflyrepository>().ImplementedBy
<butterflyrepository>().LifestylePerWebRequest());
}
}
But wait a minute -- "Whoa there, pard!" you might be saying. Isn't that a bit verbose? It's true there are other ways -- three, in fact - that you can map your interfaces (that is to say, the interfaces that are represented as argument types in your Controller's constructors): You can use XML files (which have the advantage of late binding), you can use auto-wiring or auto-registering, allowing the compiler to use reflection and register all interfaces you tell it to find, or you can do it in code, as shown above. The first way (XML) is largely considered passé today because of certain similarities with Peanut Brittle; the middle (auto-wiring) is definitely the hottest on the coolness scale (or the coolest on the...never mind); but I prefer the latter way, as shown above, for its easy readability. And, because it's easy to read, it's easy to maintain (if at times admittedly tedious) and to add grokkable logic to it.
As to the three ways of mapping your concrete classes to your interfaces, here is a (IMHO/YMMV) chart of their
qualities/maladies:
|
Grokkability |
PIA Factor* |
FancyPantsiness |
XML |
10 |
8 |
2 |
Code |
10 |
5 |
4 |
Auto-Wiring |
2 |
2 |
10 |
*Note: PIA == "Pain In the Ankle"
Enough with the scientific charts, already!
So, let's get back down to business. As to using the "Code" method: For instance (no pun intended), if you want to swap out one set of implementers with another based on some condition, it is easy to add that sort of logic:
const int HAPPINESS = 42;
int Money;
int Love;
. . .
public class RepositoriesInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
if (Love == HAPPINESS)
{
container.Register(
Component.For>imammalrepository>().ImplementedBy>
duckbilledplatypusrepository>().LifestylePerWebRequest(),
Component.For>ifishrepository>().ImplementedBy>
bottlenoseddolphinrepository>().LifestylePerWebRequest(),
Component.For>ibirdrepository>().ImplementedBy>
pterodactylrepository>().LifestylePerWebRequest(),
Component.For>ireptilerepository>().ImplementedBy>
hornedtoadrepository>().LifestylePerWebRequest());
Component.For>iinsectrepository>().ImplementedBy>
beetlerepository>().LifestylePerWebRequest());
}
else if (Money == HAPPINESS)
{
container.Register(
Component.For>imammalrepository>().ImplementedBy>
siberiantigerrepository>().LifestylePerWebRequest(),
Component.For>ifishrepository>().ImplementedBy>
catfishrepository>().LifestylePerWebRequest(),
Component.For>ibirdrepository>().ImplementedBy>
africangreyparrotrepository>().LifestylePerWebRequest(),
Component.For>ireptilerepository>().ImplementedBy>
bullfrogrepository>().LifestylePerWebRequest());
Component.For>iinsectrepository>().ImplementedBy>
prayingmantisrepository>().LifestylePerWebRequest());
}
else
{
... }
}
}
Of course, your conditional logic will differ (hopefully); and, for this to work, you will need to have created multiple (two, in this case) classes that implement each of the interfaces.
If different users should get different implementers, or if you want to use certain classes while debugging/testing and others for "production" code, this sort of scenario may come in handy for you.
That's all he wrote!