📜 ⬆️ ⬇️

Hidden dependencies as a design “smell”

Mark Siman wrote a wonderful post "Service Locator breaks encapsulation." The name of the post speaks for itself that it is dedicated to the Service Locator pattern (anti-pattern). When a programmer randomly calls an IoC container in code to resolve a dependency of an object, he uses the Service Locator anti-pattern. Mark considers 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 OrderProcessor type is broken due to two hidden dependencies that are quietly resolved in the Process method. These dependencies are hidden from the calling code, which can lead to an exception at runtime if the client has not properly configured the IoC container, determining the necessary dependencies in it. As a solution to the problem, Mark suggests transferring dependency resolution to the object's constructor.
 public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) } 

Thus, the calling code will be aware of what the OrderProcessor object actually requires.

However, in my opinion, there are still scenarios where dependency hiding can be applied. Consider a WPF application where virtually any ViewModel requires the following dependencies: IEventAggregator, IProgress, IPromptCreator . To reveal the meaning of the last two interfaces, I would add that the implementation of IProgress should be able to accept a piece of long-running code and show a window with a progress scale, IPromptCreator allows you to open windows asking for confirmation, consent or refusal (modal dialogs). Now imagine that there are ViewModels that require in addition two (and maybe three) dependencies to create a model. Here’s how the ViewModel can start to 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! Too much noise in the constructor declaration. Only two dependencies really carry useful information in terms of business logic.

If we, say, use MEF for dependency injection, 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 moved the dependencies from the constructor to the field declarations 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 dependencies in the same way as in Mark's example. Nothing really changed. Why do I think this code is not so bad? For several main reasons:

Conclusion


As I have already said, there are no only correct answers or statements for programming in all cases. Generally speaking, we should avoid using the Service Locator , because it breaks encapsulation, as Mark says in his article. Before you use the Service Locator , assess the potential harm you will cause the system. If you are sure that hidden dependency resolution does not affect client code (which your teammates can write) and there is no potential harm to the system, then go ahead with the song.

')

Source: https://habr.com/ru/post/270547/


All Articles