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.