Why "Service Locator" is an "Anti-Pattern" for Dependency Injection

Previously, we saw how we can use a Service Locator in order to prevent 'newing' of objects in our code. However, everything comes with a price. We will talk about this in detail but to give you the gist of it, a service locator is an anti-pattern as it hides the class dependencies.

Commerce Application Example

The example we are following is from our last post, however, there are some modifications. For instance, we are not using any DI container to resolve the dependencies; rather, we are using a static Service Locator to do so. 
  1. public class Commerce  
  2. {  
  3.     public void ProcessOrder(Order order)  
  4.     {  
  5.         decimal paidAmount = order.UnitPrice * order.Quantity;  
  6.         bool paymentSuccessful = Locator.Resolve<PaymentProcessor>().ProcessPayment(paidAmount);  
  7.         NotifyCustomer(paymentSuccessful);  
  8.     }  
  9.   
  10.     private void NotifyCustomer(bool paymentSuccessful)  
  11.     {  
  12.         var notifier = Locator.Resolve<NotificationManager>();  
  13.         if (paymentSuccessful)  
  14.         {  
  15.             notifier.NotifyCustomer("payment successful");  
  16.         }  
  17.         else  
  18.         {  
  19.             notifier.NotifyCustomer("payment failed");  
  20.             Locator.Resolve<Logger>().Log("payment failed");  
  21.         }  
  22.     }  
  23. }  
The Service Locator is used as a replacement for the new operator. That said, I have to admit this is no fancy locator, yet serves the purpose and looks like this.
  1. public static class Locator  
  2. {  
  3.     private readonly static Dictionary<Type, Func<object>> services  
  4.         = new Dictionary<Type, Func<object>>();  
  5.   
  6.     public static void Register<T>(Func<object> resolver)  
  7.     {  
  8.         services[typeof(T)] = resolver;  
  9.     }  
  10.   
  11.     public static T Resolve<T>()  
  12.     {  
  13.         return (T)services[typeof(T)]();  
  14.     }  
  15. }  

While everything looks fine at first sight, there are some issues with our code. Let's explore them one after another.

Using Commerce API

Assume that in the Commerce class, we see something like the following in Visual Studio.

We can clearly infer that the Visual Studio IntelliSense won't indicate if Commerce itself has any dependencies. Rather, the developer gets an impression that he/she can create a Commerce object using its default constructor. Build the application and it will surely be successful. But, what happens at the run time is quite interesting.


Commerce
object is using its default constructor. Build the application and it will surely be successful. But, what happens at the run time is quite interesting.

When the application is run, we get a
KeyNotFoundException, reason being that the developer is not aware of the fact that he/she needs to register the dependencies of Commerce class with some locator. In fact, the developer does not even know if a service locator exists.
In fact, the developer does not even know if a service locator exists.

As a result, it turns out to be a real bad developer experience with the API. And, not to mention, if I were the developer, it will surely be annoying for me. Being an anti-pattern, the service locator hides details about a class's dependencies from a developer.

Using an Abstract Service Locator

Finally, let's try to change our service locator a bit, and abstract it to an Interface. Owing to that, we have ILocator, and its concrete implementation as Locator. Given that, now it becomes necessary for the ILocator
.

With these changes in place, Visual Studio IntelliSense can now inform the user that the class is expecting an implementation of ILocator. However, this much piece of information is not enough. We are still not aware of the services used by the Commerce class.

Above all, the code in above image compiles and we can successfully call the ProcessOrder method on the object. However, at run time we will still get a KeyNotFoundException as before. This is because we are not aware of the services being used by the Commerce class.

Commerce
class, we see something like the following in Visual Studio

Commerce
object is using its default constructor. Build the application and it will surely be successful. But, what happens at the run time is quite interesting.

Commerce
class with some locator. In fact, the developer does not even know if a service locator exists.

Commerce
class.

Summary

In reality, the problem with service pattern is that it hides a class's dependencies and is a bonafide
anti-pattern. In fact, it takes away a developer's clarity about the class he/she is using. While we have so many issues with the service locator, using constructor injection can save our lives.

Not to mention, there can be a scenario where a service locator serves its purpose at its best. However, through this post, I just shared my opinion about a service locator being an anti-pattern. It would be great to receive any feedback on this topic. Please do share your opinion via comments.

Related Articles