Back

Comprendre le Factory Pattern en JavaScript

Comprendre le Factory Pattern en JavaScript

Vous avez écrit new UserService() dans douze fichiers différents. Maintenant, la signature du constructeur change, et vous devez parcourir votre base de code pour corriger chaque appel. C’est exactement le problème que résout le Factory Pattern.

Le Factory Pattern abstrait la création d’objets, vous donnant un point de contrôle unique sur la façon dont les objets sont construits. Ce n’est pas simplement une fonction qui retourne des objets — c’est un design pattern qui découple ce dont vous avez besoin de comment cela est construit.

Points clés à retenir

  • Le Factory Pattern centralise la création d’objets, facilitant la gestion des modifications de constructeurs dans votre base de code.
  • Les fonctions factory offrent une confidentialité via les closures, tandis que les classes peuvent fournir une confidentialité à l’exécution avec les champs #private et partager les méthodes via la chaîne de prototypes.
  • Combiner les factories avec l’injection de dépendances simplifie les tests en permettant la substitution facile des dépendances.
  • Utilisez les factories lorsque vous avez besoin de décisions de type à l’exécution ou d’une logique de construction complexe — évitez-les pour une création d’objets simple et stable.

Ce qu’est réellement le Factory Pattern

Une fonction factory retourne un objet. Le Factory Pattern est plus large : c’est une approche architecturale où la logique de création vit séparément du code d’utilisation.

Voici la distinction :

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

Le pattern brille lorsque vous devez décider quel objet créer en fonction de conditions à l’exécution, ou lorsque la construction nécessite une configuration que le code appelant ne devrait pas connaître.

Fonctions Factory JavaScript vs Classes

Les deux approches ont leur place. Le choix dépend de ce que vous optimisez.

Les classes avec des méthodes factory statiques fonctionnent bien lorsque vous souhaitez partager des méthodes basées sur les prototypes et des chemins d’instanciation clairs :

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

Les fonctions factory excellent lorsque vous avez besoin d’une véritable confidentialité via les closures ou que vous voulez éviter la complexité de liaison du this :

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

Le compromis : les fonctions factory créent de nouvelles instances de méthodes par objet. Les classes partagent les méthodes via la chaîne de prototypes. Pour la plupart des applications frontend, cette différence est négligeable — mais elle compte si vous créez des milliers d’instances.

Injection de dépendances avec les Factories

Les factories deviennent puissantes lorsqu’elles sont combinées avec l’injection de dépendances. Au lieu de coder en dur les dépendances, vous les passez à la 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
})

Ce pattern rend les tests simples — aucune bibliothèque de mocking requise.

Quand ne pas utiliser les Factories

Les factories ajoutent de l’indirection. C’est précieux lorsque vous avez besoin de flexibilité, mais coûteux lorsque vous n’en avez pas besoin.

Évitez la factory lorsque :

  • La création d’objets est simple et peu susceptible de changer
  • Vous ne créez qu’un seul type d’objet
  • L’abstraction ajoutée obscurcit plutôt qu’elle ne clarifie

Une factory enveloppant un simple appel new Date() n’est pas un pattern — c’est du bruit.

Patterns de création d’objets dans les modules JS modernes

Les modules ES changent la façon dont les factories s’intègrent dans l’architecture d’application. Vous pouvez exporter directement des fonctions factory configurées :

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

La frontière du module fournit une encapsulation naturelle. Les consommateurs importent la factory sans connaître ses dépendances.

Conclusion

Utilisez le Factory Pattern lorsque vous devez centraliser une logique de construction complexe, échanger des implémentations sans modifier le code appelant, injecter des dépendances pour les tests, ou décider des types d’objets à l’exécution.

Commencez par l’instanciation directe. Introduisez des factories lorsque la douleur des appels new dispersés devient réelle. Le pattern existe pour résoudre des problèmes — pas pour satisfaire des idéaux architecturaux.

FAQ

Une fonction factory retourne simplement un objet. Le Factory Pattern est une approche architecturale plus large qui sépare la logique de création du code d'utilisation, décidant souvent quel type d'objet créer en fonction de conditions à l'exécution. Le pattern fournit un point de contrôle unique pour l'instanciation d'objets dans votre application.

Utilisez des factories lorsque les signatures de constructeurs peuvent changer, lorsque vous devez décider des types d'objets à l'exécution, lorsque la construction implique une configuration complexe, ou lorsque vous souhaitez injecter des dépendances pour faciliter les tests. Pour des objets simples avec des constructeurs stables, l'instanciation directe est plus claire.

Les fonctions factory créent de nouvelles instances de méthodes pour chaque objet, tandis que les classes partagent les méthodes via la chaîne de prototypes. Cette différence est négligeable pour la plupart des applications mais devient significative lors de la création de milliers d'instances. Choisissez en fonction de vos exigences spécifiques en matière de performance et de confidentialité.

Les factories combinées à l'injection de dépendances vous permettent de passer directement des dépendances mockées à la factory pendant les tests. Cela élimine le besoin de bibliothèques de mocking ou de configurations de test complexes. Vous pouvez substituer de vrais clients API, loggers ou caches par des doublures de test simplement en passant différents arguments.

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