Mark Seemann has written a nice post “Service Locator violates encapsulation”. The name of the post speaks for itself that it’s about a pattern (anti-pattern) named Service Locator. When a programmer arbitrarily inside the code base calls for the IoC-container to resolve a dependency of an object – he uses a Service Locator anti\pattern. Mark provides the following example:
public class OrderProcessor : IOrderProcessor
{
public void Process(Order order)
{
var validator = Locator.Resolve<IOrderValidator>();
if (validator.Validate(order))
{
var shipper = Locator.Resolve<IOrderShipper>();
shipper.Ship(order);
}
}
}
As we can see the encapsulation of the type OrderProcessor is broken due to two hidden dependencies which silently resolved inside the Process method. These dependencies are hidden from a caller and that can lead to runtime exceptions in case a caller hasn’t set up the appropriate IoC-container with the required dependencies. As a solution to this problem, Mark suggests to move the resolving of the dependencies to the constructor of an object.
public class OrderProcessor : IOrderProcessor
{
public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
public void Process(Order order)
}
So now a caller is aware of what actually OrderProcessor requires. Sounds reasonable and actually I agree with that.
But there is still some room for dependency-hiding approach in my opinion. Consider a WPF-application which in almost every ViewModel requires IEventAggregator, IProgress, IPromptCreator dependencies. To reveal the meaning of the two latter interfaces I’ll say that IProgress implementation should provide the functionality of accepting a chunk of long-running code and showing a View with a progress bar, IPromptCreator provides the ability to open, well… prompts. Now imagine that there are some ViewModels which require in addition two (or maybe even three) dependencies for creating an instance of a Model. This is how a ViewModel might look like with so many dependencies:
public class PaymentViewModel: ICanPay
{
public PaymentViewModel(IPaymentSystem paymentSystem,
IRulesValidator rulesValidator,
IEventAggregator aggregator,
IProgress progress,
IPromptCreator promptCreator)
public void PayFor(Order order)
}
What a mess! There is too much noise in the constructor declaration. Just two dependencies actually carry useful information from the viewpoint of a real business value.
If we use, say, MEF for dependency injection, then we could do the following:
[Export]
public class PaymentViewModel : ICanPay
{
[Import]
protected IEventAggregator aggregator;
[Import]
protected IProgress progress;
[Import]
protected IPromptCreator promptCreator;
public PaymentViewModel(IPaymentSystem paymentSystem,
IRulesValidator rulesValidator)
{
}
public void PayFor(Order order)
{
}
}
We moved dependencies out of the constructor to the fields declaration and marked them by the attribute Import. Despite of that we don’t call for the IoC-container (though, MEF is not a an IoC-container) directly, we still hide dependencies as it was in Mark’s example. Nothing really changed. Why I think this code is not so bad? For several major reasons:
- ViewModels are not business entities, they are just chunks of glue code, no one cares of their’s dependencies too much.
- ViewModels are not public API’s and they are not reusable (in most cases)
- Because of two previous statements it may become just an agreement between team-members that ViewModels have those utility dependencies and that’s it.
- These utility dependencies are declared as protected, what allows us in tests to create a ViewModel-class which inherits from the PaymentViewModel and then replace those dependencies by mocks, cause we have access to those fields. So we don’t lose the opportunity to cover PaymentViewModel by unit-tests. It was necessary in Mark’s example (where Service Locator is used) to wire up the IoC-container in unit-test projects in order to mock or stub those dependencies and such a practice could become a pain for unit-testing process.
Conclusion
As I’ve always said, there are no single right answers or single statements which are always true. Generally speaking, we should avoid Service Locator, because it breaks encapsulation, as Mark said in his article. Before using Service Locator consider the potential harm it can bring to a system. If you are sure, that implicit dependencies resolving is irrelevant for clients (your team folks) and there’s no potential damage for a system, then go on.
Filed under:
.NET,
Best Practices,
CodeProject,
Design,
Refactoring Tagged:
ServiceLocator