I'm using Ninject, but this is not a Ninject-specific question. I'm wondering if the advanced and flexible capabilities of the IoC container are giving me enough rope to hang myself with a bad application design.
I've got a service interface:
public interface IBuilderService {
IBuilder Create(string category, string clientID);
}
and I lean on Ninject Factory Extension's ToFactory()
to create what I'd guess you'd call a proxy implementation at runtime.
I have IBuilder
bindings like this:
kernel.Bind<IBuilder>().To<NullObjectBuilder>(); // for unbound categories
kernel.Bind<IBuilder>().To<CatOneBuilder>();
kernel.Bind<IBuilder>().To<CatTwoBuilder>();
kernel.Bind<IBuilder>().To<CatThreeBuilder>();
kernel.Bind<IBuilder>().To<CatOneBuilder_ClientA>();
kernel.Bind<IBuilder>().To<CatOneBuilder_DefaultClient>(); // for unbound clients
// etc...
I've been experimenting with custom instance providers and binding generators with Ninject (I'm guessing other IoC containers may have similar constructs for overriding basic binding behavior), but I have two distinct binding scenario goals:
- if a category of binding cannot be resolved from the
IBuilderService.Create
category
argument, then aNullObjectBuilder
should be resolved - this is the default binding in this case. - if a clientID of a binding cannot be resolved, then a default client implementation like
CatOneBuilder_DefaultClient
for acategory
ofCatOne
should be resolved - this is the default binding in this case.
This is a situation where unknown category requests are expected, so it seems a shame to use expensive exception handling for something I know will happen very often. I want there to be fallback behavior to "handle the unhandled" in a very plain way - in this case, it would be the NullObjectBulder
for unknown categories, and *_DefaultClient
for unknown clients of known categories - two distinct fallback behaviors for two different types of unknown behavior.
I'm having trouble keeping the two fallback behaviors mapped to their respective scenarios, but it's given me pause to think about the friction I'm feeling in fleshing out this design…
Is it a flawed design to have default bindings and the logic used to determine when one is needed contained in IoC custom implementations, rather than an exception being thrown? Is this a mis-use of IoC containers?