Container or Service Locator
Most dependency injection (DI) frameworks today require instances to be constructed using a "factory", "object builder" or "container".
This approach also uses a lot of System.Reflection to set up properties. And eventually "bad" reflection: the one that breaks the object encapsulation and accesses private members. You may care or not. I do.
Another major drawback of the "factory" approach is that you cannot use the standard constructors.
This may be just annoying, but can become really blocking if your constructor is called from a part of the program that you don't have control on. For instance, what is you want to inject dependencies into a WinForms user object? When the user object will be added into a form, Visual Studio will instantiate it using the default constructor.
Anyway, why does the consumer of an object need to know that a special factory method should be used? Is our abstraction so shiny?
Of course, there is a solution: inside the constructor, initialize each dependency by a call to a service locator. It is not literally an "injection" of dependencies: dependencies are created from outside.
Container or Context
How does the service locator know how to create the dependency? Where does it take the configuration from, since the object got no reference to its container? Well, actually, the object has no container! The service locator has simply context-sensitive configuration. Each thread has its own context from which the service locator know how to create the dependency. Even better than contexts, stacks of contexts allow to have nested contexts just like we have nested transactions.
An elegant solution is to distinguish between global contexts (static ones) and local contexts (thread-specific). When a dependency is resolved, the service locator always looks for a match from the top of the context stack to the bottom. The stack of local contexts is always on the top of the stack of global contexts.
The use of local contexts makes it possible to execute unit tests in multiple threads while using different mocking implementations of dependencies.
For instance, the configuration of a local context may look like this:
using ( new LocalContext("Test") ) { Context.CurrentContext.Bind<ICustomerProcesses>().To<CustomerProcesses>(); Context.CurrentContext.Bind<IRentalProcesses>().To<RentalProcesses>(); TestRental test = new TestRental(); test.Go(); }
Et PostSharp dans tout ça?
The drawback of the Service Locator pattern is that you need to write boiler-plate code. For each field, you have to call the service locator.
This is true for those who don't know PostSharp. But friends of aspect-oriented programming surely noticed an opportunity to let the machine generate code for you!
Wouldn't it be nice to be able to declare a dependency just like this:
class CustomerProcesses : ICustomerProcesses { [Inject] private IRentalProcesses rentalProcesses; }
Wouldn't it be nice to have a custom attribute that adds in all constructors calls to the service locator?
Well, for those who are comfortable with PostSharp Core, it is a matter of hours to develop it. Users of PostSharp Laos will unfortunately have to wait until they get an off-the-shelf implementation. But, promised, it will come soon!
What it means for Dependency Injection frameworks?
What does it mean for DI frameworks to be compatible with this approach?
- First, they should provide an entry point for "service location", i.e. a
method like this:
T GetDependency<T>( RuntimeFieldHandle field, object declaringInstance )
- Second, their concept of container should be compatible with the idea of context. Ideally, they should support nesting and default/implicit container for the current thread (or globally).
- They should have a reasonable API for the aspect weaver to understand their semantics. That is, given a FieldInfo, the DI framework should be able to tell the weaver if the field is a dependency, and if yes of what kind, so that the weaver can decide (at compile-time) if a service location method should be called at runtime, and which. Or better: given a Type, return a list of fields and properties that should be initialized, methods to be called, events to be published or subscribed to...
Apart from these relatively small side requirements (only container nesting is a little extravagant, but it's a nice-to-have both for the container- and the context- based approach), the use of an aspect weaver does not interfere too much with the design of the DI framework. So it is perfectly possible to have to have a DI framework that is AOP-enabled, but still excellent without AOP.