I've read a couple of articles my Mark Seeman on Dependency Injection, specifically the reasons for avoiding the Service Locator pattern:
- Service Locator is an Anti-Pattern
- Service Locator violates encapsulation
- Service Locator violates SOLID
Basic idea behind Service Locator being problematic is that it can fail at runtime:
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);
}
}
}
var orderProcessor = new OrderProcessor();
// following line fails at compile time if you
// forget to register all necessary services - and
// you don't have a way of knowing which services it needs
orderProcessor.Process(someOrder);
But this means that the Composition Root must not only resolve all dependencies at startup, but actually instantiate the entire object graph, or otherwise we still wouldn't know it all the necessary dependencies have been registered:
private static void Main(string[] args)
{
var container = new WindsorContainer();
container.Kernel.Resolver.AddSubResolver(
new CollectionResolver(container.Kernel));
// Register
container.Register(
Component.For<IParser>()
.ImplementedBy<WineInformationParser>(),
Component.For<IParser>()
.ImplementedBy<HelpParser>(),
Component.For<IParseService>()
.ImplementedBy<CoalescingParserSelector>(),
Component.For<IWineRepository>()
.ImplementedBy<SqlWineRepository>(),
Component.For<IMessageWriter>()
.ImplementedBy<ConsoleMessageWriter>());
// Everything must be resolved AND instantiated here
var ps = container.Resolve<IParseService>();
ps.Parse(args).CreateCommand().Execute();
// Release
container.Release(ps);
container.Dispose();
}
How feasible is this in a real world application? Does this really mean you are not supposed to instantiate anything anywhere outside the constructor?
(Additional info)
Say you have a service which is supposed to handle incoming connections from multiple measurement devices of some kind (different connection types, protocols, or different versions of the same protocol). Whenever you get a new connection, service is supposed to construct a "pipeline" from the input port, through fifo buffers, to many parsers specific to that device type, ending with multiple consumers for various parsed messages.
Composing these object graphs in advance is something that doesn't seem possible on application startup. Even if it can be delayed, I still don't see how it's possible to get an early(-er) indication that object graph construction will fail.
This seems to be the main problem with service locators, and I don't see how to avoid it:
In short, the problem with Service Locator is that it hides a class' dependencies, causing run-time errors instead of compile-time errors, as well as making the code more difficult to maintain because it becomes unclear when you would be introducing a breaking change.