Previously, we saw how we can use a Service Locator in order to prevent the '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.
public class Commerce
{
public void ProcessOrder(Order order)
{
decimal paidAmount = order.UnitPrice * order.Quantity;
bool paymentSuccessful = Locator.Resolve<PaymentProcessor>().ProcessPayment(paidAmount);
NotifyCustomer(paymentSuccessful);
}
private void NotifyCustomer(bool paymentSuccessful)
{
var notifier = Locator.Resolve<NotificationManager>();
if (paymentSuccessful)
{
notifier.NotifyCustomer("payment successful");
}
else
{
notifier.NotifyCustomer("payment failed");
Locator.Resolve<Logger>().Log("payment failed");
}
}
}
The Service Locator is used as a replacement for the new operator. That said, I have to admit this is no fancy locator, yet it serves the purpose and looks like this.
public static class Locator
{
private readonly static Dictionary<Type, Func<object>> services
= new Dictionary<Type, Func<object>>();
public static void Register<T>(Func<object> resolver)
{
services[typeof(T)] = resolver;
}
public static T Resolve<T>()
{
return (T)services[typeof(T)]();
}
}
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 the 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.
The 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, the reason being that the developer is not aware of the fact that he/she needs to register the dependencies of the 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 really bad developer experience with the API. And, not to mention, if I were the developer, it would 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 piece of information is not enough. We are still not aware of the services used by the Commerce class.
Above all, the code in the 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.
The 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.
Summary
In reality, the problem with a 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