After much kicking and screaming, I'm starting to accept DI despite how much cleaner SL may seem as dependencies grow.
However, IMO there's still a significant show-stopper with regards to DI:
DI is not possible when you don't have control over an object's instantiation. In the ASP.NET world, examples include: HttpModule, HttpHandler, Page, etc.
In the above scenario we would resort to static service location to resolve dependencies, typically via HttpContext.Current
, which invariably infers scope from the current thread. So if we're going to use static SL here, then why not use it else where too?
Is the answer as simple as: grit your teeth and use SL when necessary (like above), but try and favor DI? And if so: doesn't using static SL just once potentially break the consistency of an entire application? Essentially undoing the hard work of DI everywhere else?
Sometimes you just can't avoid tight coupling, like in your examples. However, that doesn't mean you need to embrace it either. Instead, quarantine it by encapsulating the messiness and factoring it away from your day-to-day life.
For example, if we want the current Forms Authentication user, we don't have much choice but to access
HttpContext.Current.Request.User.Identity.Name
. We do, however, have the choice of where to make that call.HttpContext.Current
is a solution to a problem. When we call it directly from where we use the results, we are declaring the problem and solution in the same place: "I need the current user name which is declared unwaveringly as coming from the current HTTP context." This muddles the definition of both and doesn't allow for different solutions to the same problem.What we are missing is a clear articulation of the problem we are solving. For this example, it would be something like:
The fact that we are using
HttpContext.Current
, or even a user name, is not a part of the core problem definition; it is an implementation detail that only serves to complicate the code requiring the current user.We can represent the intent to retrieve the current user, sans implementation details, through an interface:
Any class where we called
HttpContext.Current
directly can now use this interface instead, injected by the container, to maintain DI goodness. It is also much more intention-revealing for a class to accept anIUserContext
in its constructor than to have a dependency which can't be seen from its public API.The implementation buries the static call in a single place where it can't harm our objects any more: