Skip to content
SP StackPractices
intermediate Por StackPractices

Patrón Registry

Centraliza el acceso a servicios y objetos compartidos vía una tabla de lookup. Un patrón structural que desacopla consumidores de implementaciones concretas.

Temas: design

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Patrón Registry

Descripción General

El Patrón Registry provee un mecanismo de lookup centralizado para servicios, configuraciones u objetos compartidos. En lugar de pasar dependencias a través de largas cadenas de constructores, los componentes solicitan lo que necesitan desde un registry. Esto desacopla a los consumidores de las implementaciones concretas que usan.

Aunque similar a un Service Locator, el Patrón Registry es más amplio: puede almacenar cualquier cosa desde conexiones a bases de datos hasta feature flags, no solo servicios. Es una alternativa pragmática a la inyección de dependencias completa cuando los frameworks DI no están disponibles.

Cuándo Usar

Usa el Patrón Registry cuando:

  • Múltiples componentes necesitan acceso al mismo recurso compartido
  • La inyección por constructor crearía listas de parámetros excesivamente largas
  • Necesitas lookup en runtime basado en configuración o contexto
  • Prefieres una alternativa liviana a un contenedor DI completo

Cuándo Evitar

  • El codebase tiene un framework DI adecuado (Spring, Angular, Dagger)
  • Los lookups del registry ocultan dependencias y dificultan las pruebas
  • El registry se convierte en un vertedero de estado global
  • Necesitas type safety en tiempo de compilación para todas las dependencias

Solución

Python

from typing import Dict, Any, Callable, TypeVar

T = TypeVar('T')

class Registry:
    _store: Dict[str, Any] = {}
    _factories: Dict[str, Callable[[], Any]] = {}

    @classmethod
    def register(cls, name: str, instance: Any):
        cls._store[name] = instance

    @classmethod
    def register_factory(cls, name: str, factory: Callable[[], Any]):
        cls._factories[name] = factory

    @classmethod
    def get(cls, name: str) -> Any:
        if name in cls._store:
            return cls._store[name]
        if name in cls._factories:
            instance = cls._factories[name]()
            cls._store[name] = instance
            return instance
        raise KeyError(f"No hay registro para: {name}")

    @classmethod
    def has(cls, name: str) -> bool:
        return name in cls._store or name in cls._factories


# Uso
class DatabaseConnection:
    def query(self, sql: str):
        return f"Resultado: {sql}"

class CacheClient:
    def get(self, key: str):
        return f"cached-{key}"

Registry.register("db", DatabaseConnection())
Registry.register_factory("cache", lambda: CacheClient())

db = Registry.get("db")
cache = Registry.get("cache")
print(db.query("SELECT 1"))
print(cache.get("users"))

Java

import java.util.*;
import java.util.function.Supplier;

public class Registry {
    private static final Map<String, Object> instances = new HashMap<>();
    private static final Map<String, Supplier<?>> factories = new HashMap<>();

    public static void register(String name, Object instance) {
        instances.put(name, instance);
    }

    public static void registerFactory(String name, Supplier<?> factory) {
        factories.put(name, factory);
    }

    @SuppressWarnings("unchecked")
    public static <T> T get(String name) {
        if (instances.containsKey(name)) {
            return (T) instances.get(name);
        }
        if (factories.containsKey(name)) {
            T instance = (T) factories.get(name).get();
            instances.put(name, instance);
            return instance;
        }
        throw new IllegalArgumentException("No hay registro para: " + name);
    }

    public static boolean has(String name) {
        return instances.containsKey(name) || factories.containsKey(name);
    }
}

// Uso
class DatabaseConnection {
    String query(String sql) { return "Resultado: " + sql; }
}

class CacheClient {
    String get(String key) { return "cached-" + key; }
}

Registry.register("db", new DatabaseConnection());
Registry.registerFactory("cache", CacheClient::new);

DatabaseConnection db = Registry.get("db");
CacheClient cache = Registry.get("cache");

JavaScript

class Registry {
  static #instances = new Map();
  static #factories = new Map();

  static register(name, instance) {
    Registry.#instances.set(name, instance);
  }

  static registerFactory(name, factory) {
    Registry.#factories.set(name, factory);
  }

  static get(name) {
    if (Registry.#instances.has(name)) {
      return Registry.#instances.get(name);
    }
    if (Registry.#factories.has(name)) {
      const instance = Registry.#factories.get(name)();
      Registry.#instances.set(name, instance);
      return instance;
    }
    throw new Error(`No hay registro para: ${name}`);
  }

  static has(name) {
    return Registry.#instances.has(name) || Registry.#factories.has(name);
  }
}

// Uso
class DatabaseConnection {
  query(sql) { return `Resultado: ${sql}`; }
}

class CacheClient {
  get(key) { return `cached-${key}`; }
}

Registry.register('db', new DatabaseConnection());
Registry.registerFactory('cache', () => new CacheClient());

const db = Registry.get('db');
const cache = Registry.get('cache');
console.log(db.query('SELECT 1'));

Explicación

El Patrón Registry tiene tres roles:

  • Registry: El mapa central que almacena instancias y funciones factory
  • Registro: Código que agrega servicios al registry al inicio
  • Lookup: Código que solicita servicios del registry en runtime

Las instancias pueden registrarse directamente o crearse lazy vía factories en el primer acceso.

Variantes

VarianteEstilo de LookupCaso de Uso
Class RegistryRegistry.get("name")Lookup simple basado en strings
Typed Registryregistry.get(DatabaseConnection.class)Type-safe con generics
Hierarchical RegistryCadena de fallback a padreRegistros hijos que sobreescriben defaults
Event Registryon(event, handler)Sistemas de event bus / pub-sub

Mejores Prácticas

  • Registra al inicio, no durante requests. El registro en runtime causa race conditions y comportamiento impredecible.
  • Usa factory registration para objetos costosos. La creación lazy evita demoras de startup para servicios que pueden no usarse.
  • Documenta las entradas del registry. Un registry compartido sin documentación se convierte en una caja de misterios.
  • Prefiere DI para código nuevo. Los registries son pragmáticos; los frameworks DI son más limpios para codebases grandes.
  • Evita la mutación en runtime. Desregistrar o re-registrar durante la operación causa bugs sutiles.

Errores Comunes

  • Registry como variable global dificulta las pruebas. Inyecta el registry o usa un wrapper testeable.
  • Lookups con strings son frágiles. Renombra una clave de servicio y cada consumidor se rompe silenciosamente.
  • Dependencias circulares en el registry causan stack overflows durante la resolución de factories.
  • Almacenar estado mutable en entradas del registry convierte al registry en una variable global oculta.
  • Sin cleanup al shutdown deja conexiones a base de datos y handles de archivos abiertos.

Ejemplos del Mundo Real

Django Settings

El objeto settings de Django es un registry de valores de configuración. Los módulos importan from django.conf import settings en lugar de pasar config a través de cada función.

WordPress Plugin API

add_action y add_filter registran callbacks en un registry global. Temas y plugins se enganchan a WordPress sin modificar archivos core.

JDBC DriverManager

DriverManager.getConnection(url) es un registry que busca el driver de base de datos apropiado basado en el prefijo de URL.

Preguntas Frecuentes

Q: Cuál es la diferencia entre Registry y Service Locator? A: Service Locator es un tipo específico de registry enfocado en resolver dependencias de servicios. Registry es más amplio y puede almacenar cualquier objeto compartido.

Q: Es Registry un anti-patrón? A: Algunos lo consideran un anti-patrón más liviano que las variables globales, pero comparte los mismos riesgos. Úsalo con moderación y prefiere DI para código nuevo.

Q: Cómo testeo código que usa un Registry? A: Limpia el registry antes de cada test, registra mocks, y ejecuta el test. Mejor aún, refactoriza para aceptar dependencias vía constructor injection.