Hidden dependencies as a “smell” of design
- Transfer
Mark Siman wrote a great post, "Service Locator breaks encapsulation." The title of the post speaks for itself that it is dedicated to the Service Locator pattern (anti-pattern) . When a programmer arbitrarily calls an IoC container in the code to resolve the dependency of an object, he uses the Service Locator anti \ pattern. Mark considers the following example:
As we can see, encapsulation of the OrderProcessor type is broken due to two hidden dependencies, which are quietly resolved in the Process method . These dependencies are hidden from the calling code, which can lead to an exception in runtime if the client has not configured the IoC container properly, having determined the necessary dependencies in it. As a solution to the problem, Mark suggests moving dependency resolution to the constructor of the object.
This way, the calling code will be aware of what the OrderProcessor object actually requires .
However, in my opinion, there are still scenarios where hiding dependencies can be applied. Consider a WPF application in which almost any ViewModel requires the following dependencies: IEventAggregator, IProgress, IPromptCreator . To reveal the meaning of the last two interfaces, I add that the IProgress implementation should be able to accept a piece of long-running code and show a window with a progress bar , IPromptCreator allows you to open windows that require confirmation, consent or rejection (modal dialogs). Now imagine that there are ViewModelswhich require in addition two (or maybe three) dependencies to create the model. Here's how a ViewModel might start to look with so many dependencies:
What a mess! Too much noise in the constructor declaration. Only two dependencies really carry useful information from the point of view of business logic.
If we, say, use MEF to inject dependencies, then we can do the following:
We transferred the dependencies from the constructor to the field declaration and marked them with the Import attribute . Despite the fact that we do not call the IoC container directly (although MEF is not a pure IoC container), we hide the dependencies in the same way as in the example of Mark. Nothing has essentially changed. Why do I think this code is not so bad? For several main reasons:
As I said, in programming there are no single right answers or statements for all occasions. Generally speaking, we should avoid using the Service Locator , as this violates encapsulation, as Mark says in his article. Before using the Service Locator , evaluate the potential damage that you cause the system. If you are sure that the hidden resolution of dependencies does not affect the client code (which your teammates can write) and there is no potential harm to the system, then go ahead with the song.
public class OrderProcessor : IOrderProcessor
{
public void Process(Order order)
{
var validator = Locator.Resolve();
if (validator.Validate(order))
{
var shipper = Locator.Resolve();
shipper.Ship(order);
}
}
}
As we can see, encapsulation of the OrderProcessor type is broken due to two hidden dependencies, which are quietly resolved in the Process method . These dependencies are hidden from the calling code, which can lead to an exception in runtime if the client has not configured the IoC container properly, having determined the necessary dependencies in it. As a solution to the problem, Mark suggests moving dependency resolution to the constructor of the object.
public class OrderProcessor : IOrderProcessor
{
public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
public void Process(Order order)
}
This way, the calling code will be aware of what the OrderProcessor object actually requires .
However, in my opinion, there are still scenarios where hiding dependencies can be applied. Consider a WPF application in which almost any ViewModel requires the following dependencies: IEventAggregator, IProgress, IPromptCreator . To reveal the meaning of the last two interfaces, I add that the IProgress implementation should be able to accept a piece of long-running code and show a window with a progress bar , IPromptCreator allows you to open windows that require confirmation, consent or rejection (modal dialogs). Now imagine that there are ViewModelswhich require in addition two (or maybe three) dependencies to create the model. Here's how a ViewModel might start to look 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! Too much noise in the constructor declaration. Only two dependencies really carry useful information from the point of view of business logic.
If we, say, use MEF to inject dependencies, then we can 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)
{
//use aggreagtor, progress, promptCreator
}
}
We transferred the dependencies from the constructor to the field declaration and marked them with the Import attribute . Despite the fact that we do not call the IoC container directly (although MEF is not a pure IoC container), we hide the dependencies in the same way as in the example of Mark. Nothing has essentially changed. Why do I think this code is not so bad? For several main reasons:
- ViewModels are not business entities; they are just pieces of code for gluing Models and Views . Nobody really cares about the above dependencies;
- ViewModels are not a public API and they are not reused (in most cases);
- Given the two previous points, team members can simply agree that all ViewModels have these utilitarian dependencies, and that’s all;
- These utility dependencies are defined as protected, which allows us to create a ViewModel class that inherits PaymentViewModel in the tests and replace the dependencies with mocks, since we will have access to those fields. Thus, we do not lose the opportunity to cover PaymentViewModel with tests . In the example of Mark (where Service Locator is used ), it was necessary to configure the IoC container in the unit test project in order to lock or stub those dependencies, and this practice can become painful for the unit testing process.
Conclusion
As I said, in programming there are no single right answers or statements for all occasions. Generally speaking, we should avoid using the Service Locator , as this violates encapsulation, as Mark says in his article. Before using the Service Locator , evaluate the potential damage that you cause the system. If you are sure that the hidden resolution of dependencies does not affect the client code (which your teammates can write) and there is no potential harm to the system, then go ahead with the song.