You might think about containers (very smart factories) and advice (to handle all the messy details).
Dear Mr. Container Sir,
Whenever I request from you an instance object of the interface ISomething,
please construct for me an instance of the concrete class SomethingImpl;
in addition, please see to it (however you do it) that, whenever I call a
method on this instance, it is wrapped within a complicated and messy try-
catch-finally which logs exceptions and mark calls as completed. That way,
all I have to do is write the business logic that goes into the SomethingImpl
and I don't have to worry about all the messy infrastuctural details.
Sincerely,
Mr. Agile.
You might see this, in code, as:
//a class that knows how to take care of the messy infrastructure details
public class MyMessyInterceptor : IInterceptor {
public void Intercept(IInvocation invocation) {
//handle the messy details of continuing with the method-invocation,
//but within a try-catch-finally that includes exception handling and
//call logging.
}
}
//a function that will configure a container (very smart factory)
public IContainer CreateContainer() {
var builder = new ContainerBuilder();
//tell the container-builder about the interceptor
builder
.Register(c => new MyMessyInterceptor())
.Named("keep-my-code-clean")
;
//tell the container what to do when you ask it for a ISomething
builder
.Register<SomethingImpl>()
.As<ISomething>()
.InterceptedBy("keep-my-code-clean")
;
return builder.BuildContainer();
}
//some function out there in your code somewhere that needs to make a
//service call; there's hundreds of functions out there just like this
//in your code, and they all just got much simpler
public object GottaGoDoSomething() {
//find the container
var container = GetTheSingletonContainerObject();
//ask for an instance of ISomething - it knows to provide a
//SomethingImpl wrapped in an interceptor that takes care of all
//the logging and exception handling
var something = container.resolve<ISomething>();
//call the big method
return something.DoSomething();
//magically (not really), the exception handling and logging are
//already taken care of
}
Coming up with the interceptor class happens just once. Registering each interceptor and service class also happens just once. Setting up the container (very smart factory) is certainly complicated.
However, every place in your code that has to use the service object, and has to embed that use within complicated and messy infrastructure details such as exception handling and logging, just got very clean and very uncomplicated. There's only one CreateContainer
, but there are hundreds of GottaGoDoSomething
s, so that's a whole lot of easy at the cost of a little bit of complicated.
(Notes: The code example uses the Autofac container framework and the Castle interceptor framework. I am aware that this is an example of the service-location pattern, not the dependency-injection pattern, but the point was to illustrate interceptors and registering them with a container, not to illustrate dependency-injection.)