patron-service-locator

Service Locator: por qué suele ser un antipatrón

  • 5 min

Un Service Locator es un registro al que un objeto consulta para obtener sus dependencias. Simplifica el acceso, pero oculta esos requisitos en el cuerpo de la clase.

En el artículo anterior vimos que la Inyección de Dependencias (DI) es la forma elegante de que nuestros objetos consigan lo que necesitan: se lo piden al constructor y alguien se lo da.

Pero existe otra forma de conseguir dependencias. Una forma más “autosuficiente”, donde el objeto, en lugar de pedir, busca lo que necesita en un registro central.

Esta técnica se llama Service Locator (Localizador de Servicios).

Aunque resuelve el mismo problema que la DI (desacoplar clases concretas), la forma en que lo hace tiene efectos secundarios peligrosos. Tanto es así, que hoy en día la mayoría de arquitectos de software lo consideran un Anti-Patrón.

¿Qué es un Service Locator?

Un Service Locator es un registro central que mantiene referencias a servicios y permite que los clientes los soliciten.

Imagina una Páginas Amarillas o una Conserjería. Si necesitas un fontanero, no esperáis a que alguien os traiga uno a casa (DI). Vais a la conserjería y decís: “Oye, dame el contacto del fontanero”.

Implementación en C#

Veamos cómo luce una implementación simple.

// El Registro Central
public static class ServiceLocator
{
    private static readonly Dictionary<Type, object> _servicios = new Dictionary<Type, object>();

    // Registrar un servicio (se hace al inicio de la app)
    public static void Register<T>(T servicio)
    {
        _servicios[typeof(T)] = servicio;
    }

    // Obtener un servicio (lo usa el cliente)
    public static T Get<T>()
    {
        try 
        {
            return (T)_servicios[typeof(T)];
        }
        catch (KeyNotFoundException)
        {
            throw new Exception($"El servicio {typeof(T).Name} no ha sido registrado.");
        }
    }
}
Copied!

Ahora, veamos cómo lo usa una clase cliente. Fíjate en la diferencia con la Inyección de Dependencias:

public class GestorPedidos
{
    private ILogger _logger;
    private IDatabase _db;

    public GestorPedidos()
    {
        // ❌ El constructor está vacío de parámetros,
        // pero por dentro está BUSCANDO las dependencias activamente.
        _logger = ServiceLocator.Get<ILogger>();
        _db = ServiceLocator.Get<IDatabase>();
    }

    public void Procesar()
    {
        _logger.Log("Procesando pedido...");
        _db.Guardar();
    }
}
Copied!

¿Por qué se considera un Anti-Patrón?

A simple vista parece cómodo. No tenemos constructores con 5 parámetros. Simplemente hacemos Get<T>() donde queramos. Pero esta comodidad se paga cara.

Oculta las Dependencias (APIs Mentirosas)

Este es el mayor pecado. Si miráis la definición de la clase:

public class GestorPedidos 
{
    public GestorPedidos() { ... }
}
Copied!

El constructor nos dice: “Hola, soy GestorPedidos y no necesito nada para funcionar”. ¡Es mentira!

Si intentas instanciar esta clase sin configurar previamente el localizador global, la aplicación fallará en tiempo de ejecución. La inyección de dependencias hace esos requisitos explícitos en el constructor.

Dificulta los Unit Tests

El Service Locator suele ser una clase estática (Singleton). El estado estático es el enemigo de los tests.

  • Si un test registra un “Mock de Base de Datos” en el Locator, ese estado se queda ahí.
  • El siguiente test podría fallar porque espera una base de datos real y se encuentra el Mock del test anterior.
  • No puedes correr tests en paralelo.

Errores en Tiempo de Ejecución vs Compilación

Con Inyección de Dependencias por constructor, si olvidas pasar un parámetro, el código no compila. El compilador te avisa.

Con Service Locator, el código compila perfectamente. El error saldrá el viernes a las 18:00 en producción cuando el código intente recuperar un servicio que nadie registró.

¿Cuándo SÍ usar Service Locator?

A pesar de todo el odio que recibe, el Service Locator no es inútil. Tiene sus nichos donde brilla o es necesario:

Desarrollo de Videojuegos (Game Dev)

En motores como Unity, el patrón Service Locator es omnipresente (ej: GetComponent<RigidBody>()). Algunos motores y frameworks ofrecen registros o contextos propios. Conviene encapsular su uso en el borde de la integración; crear miles de objetos no convierte por sí solo a la inyección por constructor en un problema de rendimiento.

Refactorización de Código Legacy

Si tienes una aplicación antigua monstruosa, pasar a Inyección de Dependencias de golpe puede ser imposible. El Service Locator puede servir como un paso intermedio para empezar a desacoplar clases sin romper todo el sistema de instanciación existente.

Extensiones de Funcionalidad

A veces extiendes un framework que controla la creación de objetos y no puedes cambiar el constructor. En esos casos, un localizador acotado puede servir como puente hacia los servicios de la aplicación.

Service Locator vs Dependency Injection

Para cerrar, una comparativa rápida para que no queden dudas:

CaracterísticaDependency Injection (DI)Service Locator (SL)
FilosofíaEl objeto recibe lo que necesita.El objeto busca lo que necesita.
DependenciasExplícitas (en el constructor).Ocultas (dentro del código).
TestabilidadAlta (fácil mockear).Baja (estado global estático).
LegibilidadVes qué necesita la clase de un vistazo.Tienes que leer el código para saber qué usa.

El Service Locator es como la comida rápida: cómodo y fácil al principio, pero si abusas de él, tu salud (la del código) se resentirá a largo plazo.

Siempre que podáis, usad Inyección de Dependencias. Dejad el Service Locator solo para casos muy específicos donde no tengáis control sobre el ciclo de vida de los objetos o el framework os obligue a ello.