Servicelocator
Patrón Service Locator en Java
Objetivo
El patrón Service Locator permite obtener servicios cuando una clase los necesita, sin depender directamente de sus implementaciones concretas. Su propósito es reducir el acoplamiento entre los clientes y los servicios.
En lugar de crear objetos como EmailService o SMSService directamente, el cliente solicita el servicio a un localizador central.
Componentes principales
- Cliente: clase que necesita utilizar un servicio.
- Service Locator: punto central que busca y devuelve servicios.
- Cache: almacena servicios ya creados para reutilizarlos.
- Initial Context: crea o localiza un servicio cuando todavía no está en la caché.
- Servicio: interfaz y clases concretas que ofrecen una funcionalidad.
Ejemplo práctico
Supongamos una aplicación que puede enviar mensajes por correo electrónico o SMS.
1. Interfaz común
public interface MessagingService {
String getMessageBody();
String getServiceName();
}
2. Implementaciones
public class EmailService implements MessagingService {
@Override
public String getMessageBody() {
return "Mensaje enviado por correo electrónico";
}
@Override
public String getServiceName() {
return "EmailService";
}
}
public class SMSService implements MessagingService {
@Override
public String getMessageBody() {
return "Mensaje enviado por SMS";
}
@Override
public String getServiceName() {
return "SMSService";
}
}
3. Contexto de creación
public class InitialContext {
public Object lookup(String serviceName) {
if (serviceName.equalsIgnoreCase("EmailService")) {
return new EmailService();
}
if (serviceName.equalsIgnoreCase("SMSService")) {
return new SMSService();
}
return null;
}
}
4. Caché de servicios
import java.util.ArrayList;
import java.util.List;
public class Cache {
private final List<MessagingService> services = new ArrayList<>();
public MessagingService getService(String serviceName) {
for (MessagingService service : services) {
if (service.getServiceName().equalsIgnoreCase(serviceName)) {
return service;
}
}
return null;
}
public void addService(MessagingService newService) {
services.add(newService);
}
}
5. Localizador de servicios
public class ServiceLocator {
private static final Cache cache = new Cache();
public static MessagingService getService(String serviceName) {
MessagingService service = cache.getService(serviceName);
if (service != null) {
return service;
}
InitialContext context = new InitialContext();
MessagingService newService =
(MessagingService) context.lookup(serviceName);
if (newService != null) {
cache.addService(newService);
}
return newService;
}
}
6. Uso desde el cliente
public class Main {
public static void main(String[] args) {
MessagingService email =
ServiceLocator.getService("EmailService");
System.out.println(email.getMessageBody());
MessagingService sms =
ServiceLocator.getService("SMSService");
System.out.println(sms.getMessageBody());
}
}
La primera vez que se solicita un servicio, el localizador lo crea y lo guarda en la caché. En solicitudes posteriores, devuelve la instancia almacenada.
Ventajas
- Reduce la dependencia directa entre el cliente y las clases concretas.
- Centraliza la obtención de servicios.
- Permite reutilizar instancias mediante una caché.
- Es sencillo de implementar en aplicaciones pequeñas.
Inconvenientes
- Las dependencias quedan ocultas dentro de la clase cliente. Con inyección de dependencias, las necesidades aparecen explícitamente en el constructor:
public class PedidoService {
private final EmailService emailService;
public PedidoService(EmailService emailService) {
this.emailService = emailService;
}
}
Aquí se ve enseguida que PedidoService depende de EmailService. Con Service Locator, la clase puede buscar el servicio internamente (aqui hay que mirar el método confirmarPedido para descubrir la dependencia entre clases):
public class PedidoService {
public void confirmarPedido() {
EmailService emailService =
ServiceLocator.getService("EmailService");
emailService.enviarConfirmacion();
}
}
- Puede dificultar las pruebas unitarias, ya que es menos directo sustituir servicios por objetos simulados (mocks).
- Los errores de configuración o de nombres de servicio pueden aparecer en tiempo de ejecución.
- Puede convertirse en un punto central demasiado acoplado si la aplicación crece.
Service Locator frente a Inyección de Dependencias
Ambos enfoques aplican el principio de Inversión de Control.
| Service Locator | Inyección de Dependencias |
|---|---|
| La clase solicita sus dependencias. | La clase recibe sus dependencias. |
| El cliente conoce el localizador. | El cliente no necesita conocer el mecanismo de creación. |
| Las dependencias pueden quedar ocultas. | Las dependencias son visibles, por ejemplo, en el constructor. |
| Puede complicar los tests. | Facilita el uso de mocks y las pruebas unitarias. |
En aplicaciones modernas y grandes suele preferirse la inyección de dependencias, por ejemplo mediante Spring. El patrón Service Locator puede ser útil cuando se necesita una solución simple o cuando no se utiliza un contenedor de dependencias.
Idea clave
El patrón Service Locator separa el uso de un servicio de su creación y localización, pero conviene usarlo con moderación porque puede ocultar dependencias y dificultar las pruebas.
Fuente
Basado en el artículo de Baeldung sobre Service Locator Pattern and Java Implementation.