Back

Entendiendo el Patrón Factory en JavaScript

Entendiendo el Patrón Factory en JavaScript

Has escrito new UserService() en doce archivos diferentes. Ahora cambia la firma del constructor y te encuentras rastreando tu código base para corregir cada llamada. Este es exactamente el problema que resuelve el Patrón Factory.

El Patrón Factory abstrae la creación de objetos, dándote un único punto de control sobre cómo se construyen los objetos. No es solo una función que devuelve objetos—es un patrón de diseño que desacopla qué necesitas de cómo se construye.

Puntos Clave

  • El Patrón Factory centraliza la creación de objetos, facilitando la gestión de cambios en constructores a lo largo de tu código base.
  • Las funciones factory proporcionan privacidad mediante closures, mientras que las clases pueden proporcionar privacidad en tiempo de ejecución con campos #private y compartir métodos a través de la cadena de prototipos.
  • Combinar factories con inyección de dependencias simplifica las pruebas al permitir la sustitución fácil de dependencias.
  • Usa factories cuando necesites decisiones de tipo en tiempo de ejecución o lógica de construcción compleja—omítelos para creación de objetos simple y estable.

Qué es Realmente el Patrón Factory

Una función factory devuelve un objeto. El Patrón Factory es más amplio: es un enfoque arquitectónico donde la lógica de creación vive separada del código de uso.

Aquí está la distinción:

// Simple factory function
const createUser = (name) => ({ name, createdAt: Date.now() })

// Factory Pattern: abstracts which class to instantiate
function createNotification(type, message) {
  const notifications = {
    email: () => new EmailNotification(message),
    sms: () => new SMSNotification(message),
    push: () => new PushNotification(message)
  }
  
  if (!notifications[type]) {
    throw new Error(`Unknown notification type: ${type}`)
  }
  
  return notifications[type]()
}

El patrón brilla cuando necesitas decidir qué objeto crear basándote en condiciones de tiempo de ejecución, o cuando la construcción requiere configuración que el código llamador no debería conocer.

Funciones Factory vs Clases en JavaScript

Ambos enfoques tienen su lugar. La elección depende de para qué estés optimizando.

Las clases con métodos factory estáticos funcionan bien cuando quieres compartir métodos basados en prototipos y rutas de instanciación claras:

class ApiClient {
  #baseUrl
  #authToken
  
  constructor(baseUrl, authToken) {
    this.#baseUrl = baseUrl
    this.#authToken = authToken
  }
  
  static forProduction(token) {
    return new ApiClient('https://api.example.com', token)
  }
  
  static forDevelopment() {
    return new ApiClient('http://localhost:3000', 'dev-token')
  }
}

Las funciones factory sobresalen cuando necesitas verdadera privacidad a través de closures o quieres evitar la complejidad del enlace de this:

function createCounter(initial = 0) {
  let count = initial
  
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  }
}

El compromiso: las funciones factory crean nuevas instancias de métodos por objeto. Las clases comparten métodos a través de la cadena de prototipos. Para la mayoría de aplicaciones frontend, esta diferencia es insignificante—pero importa si estás creando miles de instancias.

Inyección de Dependencias con Factories

Los factories se vuelven poderosos cuando se combinan con inyección de dependencias. En lugar de codificar dependencias de forma rígida, las pasas al factory:

function createUserService({ apiClient, logger, cache }) {
  return {
    async getUser(id) {
      const cached = cache.get(`user:${id}`)
      if (cached) return cached
      
      logger.debug(`Fetching user ${id}`)
      const user = await apiClient.get(`/users/${id}`)
      cache.set(`user:${id}`, user)
      return user
    }
  }
}

// In your application setup
const userService = createUserService({
  apiClient: productionApiClient,
  logger: consoleLogger,
  cache: memoryCache
})

// In tests
const testUserService = createUserService({
  apiClient: mockApiClient,
  logger: nullLogger,
  cache: noOpCache
})

Este patrón hace que las pruebas sean directas—no se requieren bibliotecas de mocking.

Cuándo No Usar Factories

Los factories añaden indirección. Eso es valioso cuando necesitas flexibilidad, pero costoso cuando no la necesitas.

Omite el factory cuando:

  • La creación de objetos es simple y es poco probable que cambie
  • Solo estás creando un tipo de objeto
  • La abstracción añadida oscurece en lugar de aclarar

Un factory que envuelve una única llamada new Date() no es un patrón—es ruido.

Patrones de Creación de Objetos en Módulos JS Modernos

Los módulos ES cambian cómo los factories encajan en la arquitectura de aplicaciones. Puedes exportar funciones factory configuradas directamente:

// services/notifications.js
import { EmailService } from './email.js'
import { config } from '../config.js'

export const createNotification = (type, message) => {
  // Factory has access to module-scoped dependencies
  const emailService = new EmailService(config.smtp)
  // ... creation logic
}

El límite del módulo proporciona encapsulación natural. Los consumidores importan el factory sin conocer sus dependencias.

Conclusión

Usa el Patrón Factory cuando necesites centralizar lógica de construcción compleja, intercambiar implementaciones sin cambiar el código llamador, inyectar dependencias para pruebas, o decidir tipos de objetos en tiempo de ejecución.

Comienza con instanciación directa. Introduce factories cuando el dolor de las llamadas new dispersas se vuelva real. El patrón existe para resolver problemas—no para satisfacer ideales arquitectónicos.

Preguntas Frecuentes

Una función factory simplemente devuelve un objeto. El Patrón Factory es un enfoque arquitectónico más amplio que separa la lógica de creación del código de uso, a menudo decidiendo qué tipo de objeto crear basándose en condiciones de tiempo de ejecución. El patrón proporciona un único punto de control para la instanciación de objetos en toda tu aplicación.

Usa factories cuando las firmas de constructores puedan cambiar, cuando necesites decidir tipos de objetos en tiempo de ejecución, cuando la construcción involucre configuración compleja, o cuando quieras inyectar dependencias para facilitar las pruebas. Para objetos simples con constructores estables, la instanciación directa es más limpia.

Las funciones factory crean nuevas instancias de métodos para cada objeto, mientras que las clases comparten métodos a través de la cadena de prototipos. Esta diferencia es insignificante para la mayoría de aplicaciones pero se vuelve significativa al crear miles de instancias. Elige basándote en tus requisitos específicos de rendimiento y necesidades de privacidad.

Los factories combinados con inyección de dependencias te permiten pasar dependencias mock directamente al factory durante las pruebas. Esto elimina la necesidad de bibliotecas de mocking o configuración de pruebas compleja. Puedes sustituir clientes API reales, loggers o cachés con dobles de prueba simplemente pasando diferentes argumentos.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay