Service locator pattern is one of the most commonly used patterns. In general, this pattern is used along with other patterns like Adapter Pattern, Observer Pattern, Dependency Injection Principles and many more. In this post, however, we will be talking about its use with dependency injection, which is one its many uses.
Introduction
The service locator pattern works as a middle man between the application and the services used by it. The intent is to unbind the application from the services it uses. The application rather requests the locator to provide a service it requires at any point of time. However, an application may have a dedicated or a generic locator depending on the application requirements. A dedicated service locator serves the requests for a particular service, whereas a generic locator serves all the requests.
Scenario
In our previous post, we used Autofac in order to manage the dependencies of Commerce class. It's equally important to note, that our Commerce class may have some methods which use either of the dependencies or do not use any of them at all. And therefore, instantiating all the dependencies at once doesn't seem to be a good idea.
At this instant, a better approach to such a scenario would be, to instantiate a dependency
"only" when it is required. Unlike instantiating all the dependencies at once, a service locator instantiates a dependency/service as and when required. Consequently, saving the run time manager from the burden of managing in-memory dependency objects, which may never be used in the application again.
Implementing a Generic Service Locator
As can be seen in the code below, our Commerce class has multiple dependencies. The container instantiates all these dependencies for our first request of Commerce class object. And therefore, we use the service locator pattern to optimize our code for such scenarios.
- public class Commerce
- {
- private readonly PaymentProcessor _paymentProcessor;
- private readonly NotificationManager _notificationManager;
- private readonly Logger _logger;
-
- public Commerce(PaymentProcessor paymentProcessor, NotificationManager notificationManager, Logger logger)
- {
- _paymentProcessor = paymentProcessor;
- _notificationManager = notificationManager;
- _logger = logger;
- }
-
- public void ProcessOrder(Order order)
- {
- decimal paidAmount = order.UnitPrice * order.Quantity;
- bool paymentSuccessful = _paymentProcessor.ProcessPayment(paidAmount);
-
- if(paymentSuccessful)
- {
- _notificationManager.NotifyCustomer(notification: "payment successful");
- }
- else
- {
- _notificationManager.NotifyCustomer(notification: "payment failed");
- _logger.Log(errorMessage: "payment failed");
- }
- }
- }
We have a generic service locator as IServiceLocator, which takes a generic at method level. There can be many ways in which the locator locates the requested service. For instance, it may use some configuration file or use a DI container (as in our case) to locate the service requested.
- public interface IServiceLocator
- {
- TContract Locate<TContract>();
- }
-
- public class ServiceLocator : IServiceLocator
- {
- public TContract Locate<TContract>()
- {
- return Program.Container.Resolve<TContract>();
- }
- }
Additionally, we have new implementation of the Commerce class as NewCommerce class, in order to better understand the use of a locator. It is equally important to note that we need to explicitly register the ServiceLocator as IServiceLocator with the container.
- public class NewCommerce
- {
- private readonly IServiceLocator _serviceLocator;
- public NewCommerce(IServiceLocator serviceLocator)
- {
- _serviceLocator = serviceLocator;
- }
-
- public void ProcessOrder(Order order)
- {
- decimal paidAmount = order.UnitPrice * order.Quantity;
- bool paymentSuccessful = _serviceLocator.Locate<PaymentProcessor>().ProcessPayment(paidAmount);
- NotifyCustomer(paymentSuccessful);
- }
-
- private void NotifyCustomer(bool paymentSuccessful)
- {
- var notifier = _serviceLocator.Locate<NotificationManager>();
- if (paymentSuccessful)
- {
- notifier.NotifyCustomer("payment successful");
- }
- else
- {
- notifier.NotifyCustomer("payment failed");
- _serviceLocator.Locate<Logger>().Log("payment failed");
- }
- }
- }
Summary
- As a result of implementing the service locator pattern, our NewCommerce class has a single dependency of IServiceLocator.
- The DI container does not instantiate all the services used by the NewCommerce class.
- The service locator instantiates a dependency only when it is required.
- Using a service locator with DI, is one of its many uses.
- A service locator may use some configuration file, in order to locate the service.
Related Articles