Factory Pattern
Pattern de conception créationnel qui fournit une interface pour créer des objets sans spécifier leur classe concrète, améliorant la flexibilité du code.
Mis à jour le 9 janvier 2026
Le Factory Pattern est un patron de conception créationnel qui délègue la responsabilité d'instanciation d'objets à une classe ou méthode spécialisée. Au lieu d'utiliser directement l'opérateur 'new', le code client fait appel à une factory qui détermine quelle classe concrète instancier. Ce pattern est particulièrement utile lorsque le processus de création est complexe ou lorsque le type exact d'objet à créer n'est connu qu'au moment de l'exécution.
Fondements du Pattern
- Encapsule la logique de création d'objets dans une méthode ou classe dédiée
- Retourne des instances via une interface commune plutôt que des classes concrètes
- Permet de respecter le principe SOLID d'ouverture/fermeture (Open/Closed Principle)
- Découple le code client des implémentations concrètes, réduisant les dépendances directes
Avantages Principaux
- Flexibilité accrue : facilite l'ajout de nouveaux types sans modifier le code existant
- Testabilité améliorée : permet d'injecter des mocks ou stubs facilement
- Centralisation de la logique de création complexe (validation, initialisation, pooling)
- Réduction du couplage entre composants, favorisant une architecture modulaire
- Meilleure maintenabilité grâce à un point unique de modification pour la création d'objets
Exemple Concret
Voici une implémentation TypeScript d'une factory pour différents types de notifications système :
// Interface commune pour tous les types de notifications
interface Notification {
send(message: string, recipient: string): Promise<void>;
validate(recipient: string): boolean;
}
// Implémentations concrètes
class EmailNotification implements Notification {
async send(message: string, recipient: string): Promise<void> {
console.log(`Sending email to ${recipient}: ${message}`);
// Logique SMTP ici
}
validate(recipient: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
}
}
class SMSNotification implements Notification {
async send(message: string, recipient: string): Promise<void> {
console.log(`Sending SMS to ${recipient}: ${message}`);
// Logique API SMS ici
}
validate(recipient: string): boolean {
return /^\+?[1-9]\d{1,14}$/.test(recipient);
}
}
class PushNotification implements Notification {
async send(message: string, recipient: string): Promise<void> {
console.log(`Sending push to device ${recipient}: ${message}`);
// Logique Firebase/APNs ici
}
validate(recipient: string): boolean {
return recipient.length > 20; // Token validation simplifiée
}
}
// Factory Pattern
class NotificationFactory {
static create(type: 'email' | 'sms' | 'push'): Notification {
switch (type) {
case 'email':
return new EmailNotification();
case 'sms':
return new SMSNotification();
case 'push':
return new PushNotification();
default:
throw new Error(`Type de notification non supporté: ${type}`);
}
}
}
// Utilisation
async function notifyUser(type: 'email' | 'sms' | 'push', recipient: string, message: string) {
const notification = NotificationFactory.create(type);
if (!notification.validate(recipient)) {
throw new Error('Destinataire invalide');
}
await notification.send(message, recipient);
}
// Le code client n'a pas besoin de connaître les classes concrètes
await notifyUser('email', 'user@example.com', 'Bienvenue!');
await notifyUser('sms', '+33612345678', 'Code de vérification: 1234');Mise en Œuvre
- Identifier les classes partageant une interface ou classe de base commune
- Définir l'interface ou classe abstraite représentant le contrat commun
- Créer les implémentations concrètes respectant cette interface
- Développer la classe Factory avec une méthode de création (statique ou d'instance)
- Implémenter la logique de sélection (switch, map, stratégie de configuration)
- Remplacer les instanciations directes dans le code client par des appels à la factory
- Ajouter une gestion d'erreur pour les types non supportés ou paramètres invalides
Conseil Professionnel
Pour des cas complexes avec de nombreux types, privilégiez un registre de constructeurs (Map<string, Constructor>) plutôt qu'un switch. Cela permet d'ajouter de nouveaux types dynamiquement sans modifier la factory elle-même. Combinez avec le pattern Strategy pour des comportements post-création (initialisation, configuration, pooling).
Outils et Extensions
- InversifyJS : framework d'injection de dépendances avec support natif des factories
- TypeDI : conteneur DI TypeScript permettant de définir des factories personnalisées
- NestJS Factory Providers : système de providers avec support des factories asynchrones
- Autofac (C#/.NET) : conteneur IoC avec delegates de factory très puissants
- Spring Framework : annotation @Bean et FactoryBean pour Java/Kotlin
Le Factory Pattern constitue un pilier de l'architecture logicielle moderne en permettant de créer des systèmes évolutifs et maintenables. En centralisant la logique de création, il réduit significativement le coût de modification et d'extension des applications. Son adoption stratégique dans les couches métier critiques améliore la qualité du code tout en accélérant le développement de nouvelles fonctionnalités grâce à sa structure extensible.
