Back

Entendendo o Factory Pattern em JavaScript

Entendendo o Factory Pattern em JavaScript

Você escreveu new UserService() em doze arquivos diferentes. Agora a assinatura do construtor muda, e você está vasculhando sua base de código corrigindo cada chamada. Este é exatamente o problema que o Factory Pattern resolve.

O Factory Pattern abstrai a criação de objetos, fornecendo um único ponto de controle sobre como os objetos são construídos. Não é apenas uma função que retorna objetos—é um padrão de design que desacopla o que você precisa de como isso é construído.

Principais Conclusões

  • O Factory Pattern centraliza a criação de objetos, facilitando o gerenciamento de mudanças no construtor em toda a sua base de código.
  • Funções factory fornecem privacidade via closures, enquanto classes podem fornecer privacidade em tempo de execução com campos #private e compartilhar métodos via cadeia de protótipos.
  • Combinar factories com injeção de dependência simplifica os testes ao permitir a substituição fácil de dependências.
  • Use factories quando precisar de decisões de tipo em tempo de execução ou lógica de construção complexa—evite-as para criação de objetos simples e estáveis.

O Que o Factory Pattern Realmente É

Uma função factory retorna um objeto. O Factory Pattern é mais amplo: é uma abordagem arquitetural onde a lógica de criação vive separadamente do código de uso.

Aqui está a distinção:

// 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]()
}

O padrão brilha quando você precisa decidir qual objeto criar com base em condições de tempo de execução, ou quando a construção requer configuração que o código chamador não deveria conhecer.

Funções Factory vs Classes em JavaScript

Ambas as abordagens têm seu lugar. A escolha depende do que você está otimizando.

Classes com métodos factory estáticos funcionam bem quando você quer compartilhamento de métodos baseado em protótipo e caminhos de instanciação claros:

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')
  }
}

Funções factory se destacam quando você precisa de verdadeira privacidade através de closures ou quer evitar a complexidade de binding do this:

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

O trade-off: funções factory criam novas instâncias de métodos por objeto. Classes compartilham métodos via cadeia de protótipos. Para a maioria das aplicações frontend, essa diferença é insignificante—mas importa se você está criando milhares de instâncias.

Injeção de Dependência com Factories

Factories se tornam poderosas quando combinadas com injeção de dependência. Em vez de codificar dependências diretamente, você as passa para a 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 padrão torna os testes diretos—nenhuma biblioteca de mocking necessária.

Quando Não Usar Factories

Factories adicionam indireção. Isso é valioso quando você precisa de flexibilidade, mas custoso quando não precisa.

Evite a factory quando:

  • A criação de objetos é simples e improvável de mudar
  • Você está criando apenas um tipo de objeto
  • A abstração adicionada obscurece em vez de clarificar

Uma factory envolvendo uma única chamada new Date() não é um padrão—é ruído.

Padrões de Criação de Objetos em Módulos JS Modernos

Módulos ES mudam como factories se encaixam na arquitetura da aplicação. Você pode exportar funções factory configuradas diretamente:

// 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
}

O limite do módulo fornece encapsulamento natural. Consumidores importam a factory sem conhecer suas dependências.

Conclusão

Use o Factory Pattern quando precisar centralizar lógica de construção complexa, trocar implementações sem mudar o código chamador, injetar dependências para testes, ou decidir tipos de objetos em tempo de execução.

Comece com instanciação direta. Introduza factories quando a dor de chamadas new espalhadas se tornar real. O padrão existe para resolver problemas—não para satisfazer ideais arquiteturais.

Perguntas Frequentes

Uma função factory simplesmente retorna um objeto. O Factory Pattern é uma abordagem arquitetural mais ampla que separa a lógica de criação do código de uso, frequentemente decidindo qual tipo de objeto criar com base em condições de tempo de execução. O padrão fornece um único ponto de controle para instanciação de objetos em toda a sua aplicação.

Use factories quando assinaturas de construtores podem mudar, quando você precisa decidir tipos de objetos em tempo de execução, quando a construção envolve configuração complexa, ou quando você quer injetar dependências para facilitar os testes. Para objetos simples com construtores estáveis, a instanciação direta é mais limpa.

Funções factory criam novas instâncias de métodos para cada objeto, enquanto classes compartilham métodos via cadeia de protótipos. Esta diferença é insignificante para a maioria das aplicações, mas se torna significativa ao criar milhares de instâncias. Escolha com base nos seus requisitos específicos de desempenho e necessidades de privacidade.

Factories combinadas com injeção de dependência permitem que você passe dependências mock diretamente para a factory durante os testes. Isso elimina a necessidade de bibliotecas de mocking ou configuração complexa de testes. Você pode substituir clientes API reais, loggers ou caches por test doubles simplesmente passando argumentos diferentes.

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