Back to: Java Design Patterns
Service Locator Design Pattern in Java
In this article, I am going to discuss Service Locator Design Pattern in Java with Examples. Please read our previous article where we discussed Front Controller Design Patterns in Java. In this article, we will dive into the Service Locator Design Pattern in Java, its advantages, disadvantages, and practical applications in software development.
What is Service Locator Design Pattern?
In software development, managing dependencies and accessing services can become challenging as applications grow in complexity. The Service Locator design pattern offers a solution to centralize and simplify the retrieval of services and dependencies.
The Service Locator design pattern aims to decouple service consumers from the concrete implementations of the services they depend on. It provides a centralized registry, known as the Service Locator, which manages the creation and retrieval of services. The Service Locator acts as a mediator between service consumers and the underlying service implementations.
Components of Service Locator Design Pattern
The Service Locator Design Pattern consists of the following components:
- Service Locator: It serves as a central registry that maintains a mapping between service interfaces and their concrete implementations. The Service Locator provides a method to retrieve services based on their interface, abstracting away the details of service creation and instantiation.
- Service: It represents an interface or an abstract class that defines the contract for a particular service. Concrete implementations of these services are registered with the Service Locator.
Example to Understand Service Locator Design Pattern in Java
To understand the Service Locator design pattern in a real-world context, let’s consider a logging system in a software application. The application requires logging services to record and track events, errors, and other relevant information. The Service Locator pattern can be employed to centralize the management and retrieval of the logging service.
In this scenario, the Service Locator acts as a central registry that maps the logging service interface to its concrete implementation. The application’s components, such as modules, classes, or methods, can request the logging service through the Service Locator without being tightly coupled to a specific logging implementation.
During the initialization phase, the Service Locator is configured with the desired logging service implementation. This configuration could be through manual registration or by using a configuration file. The logging service implementation can be swapped without modifying the application’s codebase, as long as the new implementation adheres to the logging service interface.
Whenever a component in the application requires logging functionality, it can access the logging service through the Service Locator. The Service Locator resolves the request by retrieving the appropriate logging service implementation based on the registered configuration. The component can then use the logging service to record relevant information.
The Service Locator pattern provides flexibility by allowing the application to switch logging implementations at runtime. For example, during testing, a mock logging service implementation can be registered in the Service Locator to facilitate unit testing without the need for actual log writes. This enables the application to decouple itself from specific logging frameworks or libraries and easily adapt to changing logging requirements. The UML Diagram of this example is given below using Service Locator Design Pattern.
Implementing Service Locator Design Pattern in Java
Step 1: Create a new directory to store all the class files of this project.
Step 2: Open VS Code and create a new project, called servicelocator.
Step 3: In the project, create a new file called Service.java. Add the following code to the file:
public interface Service { public String getName(); public void execute(); }
This is the interface that will be implemented by other concrete classes.
Step 4: Create two new files, called ServiceA.java and ServiceB.java. Both of these are concrete classes that implement the Service interface. Add the following code to the files:
ServiceA.java
public class ServiceA implements Service { @Override public String getName() {return "Service A";} @Override public void execute() {System.out.println("Service A is executing!");} }
ServiceB.java
public class ServiceB implements Service { @Override public String getName() {return "Service B";} @Override public void execute() {System.out.println("Service B is executing!");} }
Step 5: In the project, create a new file called Context.java. This class is responsible for creating new services, based on client requests. Add the following code to Context.java:
public class Context { public Service lookUp (String name) { if (name.equalsIgnoreCase("service a")) { System.out.println("Creating new service A"); return new ServiceA(); } else if (name.equalsIgnoreCase("service b")) { System.out.println("Creating new service B"); return new ServiceB(); } return null; } }
Step 6: In the project, create a new file called Cache.java. This class will store new services as they are created. We the same service is needed again, the cached service will be sent, rather than creating a new service altogether. Add the following code to Cache.java:
import java.util.ArrayList; import java.util.List; public class Cache { private List<Service> services; public Cache() {services = new ArrayList<>();} public Service getService(String name) { for (Service s : services) { if (s.getName().equalsIgnoreCase(name)) { System.out.println("Returning cached " + s.getName()); return s; } } return null; } public void addService (Service newService) { boolean exists = false; for (Service s : services) if (s.getName().equalsIgnoreCase(newService.getName())) exists = true; if (!exists) services.add(newService); } }
Step 7: In the project, create a new file called ServiceLocator.java. This class is responsible for communication between the main class and the Cache class. Add the following code to ServiceLocator.java:
public class ServiceLocator { private static Cache c; static {c = new Cache();} public static Service getService(String name) { Service s = c.getService(name); if (s == null) { s = new Context().lookUp(name); c.addService(s); } return s; } }
Step 8: In the project, create a new file called ServiceLocatorPatternDemo.java. This class will contain the main() function. Add the following code to ServiceLocatorPatternDemo.java:
public class ServiceLocatorPatternDemo { public static void main(String[] args) { Service s = ServiceLocator.getService("service a"); s.execute(); s = ServiceLocator.getService("service b"); s.execute(); } }
The main() function executes two services, one for each type of service.
Step 9: Compile and execute the application. Ensure compilation is successful. Verify that the program works as expected.
Congratulations! You now know how to implement Service Locator Design Pattern in Java!
UML Diagram of Service Locator Design Pattern:
Now, let us see the Service Locator Design Pattern UML Diagram Components with our Example so that you can easily understand the UML Diagram.
The classes can be described as follows:
- Object: This interface defines the functions that will be later implemented by the concrete object class.
- ConcreteObject: This class implements the functions defined in the aforementioned object interface.
- Context: This class is responsible for creating new objects when required.
- Cache: This class is responsible for storing objects as they are created. It should also return the object if the object is required again.
- ServiceLocator: This class is responsible for the communication between the DriverClass and Cache.
- DriverClass: This class contains the main() function and is responsible for the simulation of the program.
Advantages of Service Locator Design Pattern in Java:
The followings are the advantages of using the Service Locator Design Pattern in Java:
- Centralized Service Management: The Service Locator pattern provides a centralized location to manage the creation and retrieval of services. By abstracting away, the creation details, it simplifies the process of obtaining services for service consumers. Developers can register services once and reuse them throughout the application, reducing code duplication and improving maintainability.
- Decoupling Service Consumers and Implementations: The Service Locator decouples service consumers from the concrete implementations of services. Service consumers only need to know the interface or abstract class of a service they require, rather than being tightly coupled to a specific implementation. This promotes loose coupling and allows for an easier substitution or modification of service implementations without impacting the clients.
- Runtime Service Configuration: The Service Locator pattern allows for the runtime configuration of services. Services can be registered and replaced dynamically, making it easier to adapt to changing requirements or swap implementations based on specific conditions. This flexibility enables applications to evolve and adapt without requiring significant changes to the client code.
- Dependency Injection Support: The Service Locator pattern complements dependency injection frameworks by providing a mechanism to locate and retrieve dependencies. It can be used in conjunction with frameworks like Spring or Google Guice, enhancing the flexibility and manageability of dependency injection in large-scale applications.
Disadvantages of Service Locator Design Pattern in Java:
The followings are the disadvantages of using the Service Locator Design Pattern in Java:
- Dependency on the Service Locator: The Service Locator pattern introduces a dependency on the Service Locator itself. Service consumers need to access the Service Locator to obtain the required services, which can create a direct coupling between the consumers and the Service Locator. This can make the code harder to test and maintain as the Service Locator becomes a central point of coordination.
- Reduced Visibility of Dependencies: The Service Locator pattern may obscure the dependencies of service consumers. Since the Service Locator handles the creation and retrieval of services, the dependencies may not be clearly visible from the consumer’s code. This can make it more difficult to reason about the dependencies and can lead to hidden dependencies that are harder to manage.
- Potential Runtime Issues: The Service Locator pattern can introduce runtime issues if the services are not properly managed or if there are conflicting dependencies. Changes to the registered services or their implementations at runtime may lead to unexpected behavior and can be challenging to debug. Careful management of service registrations and proper testing is necessary to avoid such issues.
- Hidden Dependencies: The use of the Service Locator pattern can lead to hidden dependencies in the codebase. Since services are obtained through the Service Locator, the dependencies may not be explicitly defined in the constructor or method signatures. This can make it more challenging to understand and reason about the code, leading to potential maintainability issues.
Real-Time use cases of Service Locator Design Pattern
The Service Locator pattern finds practical applications in various software development scenarios such as:
- Frameworks and Libraries: Many frameworks and libraries use the Service Locator pattern to manage and provide access to their core services or components. For example, in the Java EE platform, the Java Naming and Directory Interface (JNDI) acts as a Service Locator to locate resources such as data sources, messaging queues, or remote services.
- Large-Scale Applications: In large-scale applications with a significant number of dependencies, the Service Locator pattern can help centralize the management and retrieval of services. It simplifies the wiring and configuration of dependencies, making it easier to manage complex dependency graphs.
- Unit Testing: The Service Locator pattern can facilitate unit testing by allowing the substitution of services with test doubles or mock implementations. Test-specific service implementations can be registered in the Service Locator during testing, enabling isolation and control over dependencies in unit tests.
In the next article, I am going to discuss Transfer Object Design Pattern in Java with Examples. Here, in this article, I try to explain Service Locator Design Pattern in Java with Examples. I hope you understood the need for and use of the Service Locator Design Pattern in Java.