I have an interface called IContext
. For the purposes of this it doesn't really matter what's it does except the following:
T GetService<T>();
What this method does is look at the current DI container of the application and attempts to resolve the dependency. Fairly standard I think.
In my ASP.NET MVC application, my constructor looks like this.
protected MyControllerBase(IContext ctx)
{
TheContext = ctx;
SomeService = ctx.GetService<ISomeService>();
AnotherService = ctx.GetService<IAnotherService>();
}
So rather than adding multiple parameters in the constructor for each service (because this will get really annoying and time-consuming for the developers extending the application) I am using this method to get services.
Now, it feels wrong. However, the way I'm currently justifying it in my head is this – I can mock it.
I can. It wouldn't be difficult to mock up IContext
to test the Controller. I'd have to anyway:
public class MyMockContext : IContext
{
public T GetService<T>()
{
if (typeof(T) == typeof(ISomeService))
{
// return another mock, or concrete etc etc
}
// etc etc
}
}
But as I said, it feels wrong. Any thoughts / abuse welcome.
Best Answer
Having one instead of many parameters in the constructor is not the problematic part of this design. As long as your
IContext
class is nothing but a service facade, specificially for providing the dependencies used inMyControllerBase
, and not a general service locator used throughout your whole code, that part of your code is IMHO ok.Your first example might be changed to
that would not be a substantial design change of
MyControllerBase
. If this design is good or bad depends only on the fact if your want toTheContext
,SomeService
andAnotherService
are always all initialized with mock objects, or all of them with real objectsSo using only one parameter instead of 3 in the constructor can be fully reasonable.
The thing which is problematic is
IContext
exposing theGetService
method in public. IMHO you should avoid this, instead keep the "factory methods" explicit. So will it be ok to implement theGetSomeService
andGetAnotherService
methods from my example using a service locator? IMHO that depends. As long theIContext
class keeps just beeing a simple abstract factory for the specific purpose of providing an explicit list of service objects, that is IMHO acceptable. Abstract factories are typically just "glue" code, which don't have to be unit-tested itself. Nethertheless you should ask yourself if in the context of methods likeGetSomeService
, if you really need a service locator, or if an explicit constructor call would not be simpler.So beware, when you stick to a design where the
IContext
implementation is just a wrapper around a public, genericGetService
method, allowing to resolve any arbitrary depencencies by arbitrary classes, then everything applies what @BenjaminHodgson wrote in his answer.