This post is part of the series SOLID Wash Tunnel.

Definition

"A Service Locator supplies application components outside the Composition Root with access to an unbounded set of Dependencies."

- Steven van Deursen, Mark Seemann

Service Locator allows one to get hold of a reference to a dependecy that you depend on. This is done in cases where:

  • You can not (somehow) inject said dependecy.
  • The amount of dependecies you need to inject is overwhelming.

The reason why it is considered by many to be an anti-pattern is because it hides the dependencies of a class, causing run-time errors instead of compile-time errors. It also becomes unclear when you would be introducing a breaking change.


Implementation

Motherboard

We have already introduced the Motherboard when we talked about the mediator pattern. The Motherboard is the concrete implementation of ISignalTransmitter which was used to find the appropriate ISignalHandler/s for a given ISignal.

When a new signal is transmitted the motherboard class use the injected IContainer (the service locator in this case) in order to resolve the appropriate ISignalHandler/s based on the generic T parameter.

public class Motherboard : ISignalTransmitter
{
private readonly IContainer _container;

public Motherboard(IContainer container)
{
_container = container;
}

public void Transmit<T>(T signal) where T : ISignal
{
var signalHandlers = GetSignalHandlers<T>();
foreach (var handler in signalHandlers)
{
handler.Handle(signal);
}
}

private IEnumerable<ISignalHandler<T>> GetSignalHandlers<T>() where T : ISignal
=> Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(ISignalHandler<T>).IsAssignableFrom(type)
&& !type.IsInterface && !type.IsAbstract)
.Select(type => (ISignalHandler<T>)_container.GetService(type));
}

Alternatives

"What would be some alternative ways to deliver the same functionality without the service locator, and what would be their drawbacks?"

There are two ways I can think of to remove the IContainer dependecy, so that we would remove the service locator.

Constructor Injection

Injecting an IEnumerable<ISignalHandler<T>> in the Motherboard constructor. This would require modification of the ISignalTransmitter interface too.

public interface ISignalTransmitter<T> 
where T : ISignal
{
void Transmit(T signal);
}

public class Motherboard<T> : ISignalTransmitter<T>
where T : ISignal
{
private readonly IEnumerable<ISignalHandler<T>> _signalHandlers;

public Motherboard(IEnumerable<ISignalHandler<T>> signalHandlers)
{
_signalHandlers = signalHandlers;
}

public void Transmit(T signal)
{
foreach (var handler in _signalHandlers)
{
handler.Handle(signal);
}
}
}

The drawback would be that the clients which transmit multiple signals like UserPanel would require multiple dependecies of ISignalTransmitter<T>, and also would have to specify the type T.

  • To transmit an WashProgramSelectedSignal signal, the UserPanel would require a dependecy on ISignalTransmitter<WashProgramSelectedSignal>.
  • To transmit an VehicleWashingStartedSignal signal, the UserPanel would require a dependecy on ISignalTransmitter<VehicleWashingStartedSignal>.
  • And on and on...

This would make the clients life miserable, as opposed to having a need for a single dependecy on the ISignalTransmitter.

Method Injection

Injecting an IEnumerable<ISignalHandler<T>> in the Transmit method along with the actual signal. This would require modification of the ISignalTransmitter interface too.

public interface ISignalTransmitter
{
void Transmit<T>(T signal, IEnumerable<ISignalHandler<T>> signalHandlers)
where T : ISignal;
}

public class Motherboard : ISignalTransmitter
{
public void Transmit<T>(T signal, IEnumerable<ISignalHandler<T>> signalHandlers)
where T : ISignal
{
foreach (var handler in signalHandlers)
{
handler.Handle(signal);
}
}
}

The drawback would be that the clients would have to supply the implementations of the ISignalHandler's themselves, which in turn throws loose coupling out the window.

Closing Thoughts

In both alternatives, the benefit of removing the service locator from Motherboard simply don't outweigh the drawbacks in my opinion!

In general my advise would be to avoid the Service Locator. But in the other hand, just because it is considered to be an anti-pattern does not neccessarily make it "the evil". There are usecases like the one with the motherboard that makes it the best choice. Always considering what 'best choice' means to you the reader!


Continue the series on Fluent Builder (part 1/3).

If you found this article helpful please give it a share in your favorite forums 😉.
The solution project is available at GitHub.