MFormations
Modern Design Patterns

Chapitre 3

Chapitre 03 — Patterns Factory (approfondi)

Chapitre 03 — Patterns Factory (approfondi)

Cours 03 — Patterns Factory (approfondi)

Partie 1 : Simple Factory

1.1 Définition

La Simple Factory (ou Static Factory) est un idiome, pas un pattern GoF : une classe statique qui crée des objets selon un paramètre.

Diagramme en cours de génération...

1.2 Exemple

interface PaymentProvider {
  process(amount: number): void;
}

class StripeProvider implements PaymentProvider {
  process(amount: number): void {
    console.log(`Stripe: $${amount}`);
  }
}

class PayPalProvider implements PaymentProvider {
  process(amount: number): void {
    console.log(`PayPal: $${amount}`);
  }
}

// Simple Factory
class PaymentProviderFactory {
  static create(type: "stripe" | "paypal"): PaymentProvider {
    switch (type) {
      case "stripe": return new StripeProvider();
      case "paypal": return new PayPalProvider();
    }
  }
}

// Usage
const provider = PaymentProviderFactory.create("stripe");
provider.process(100);

1.3 Limites

  • Violation OCP : ajouter un provider = modifier la factory
  • Pas d'héritage : pas de personnalisation possible
  • Testabilité : difficile à mocker

Partie 2 : Factory Method

2.1 Définition (GoF)

"Définir une interface pour créer un objet, mais laisser les sous-classes décider quelle classe instancier. La Factory Method permet à une classe de reporter l'instanciation à ses sous-classes."

2.2 Structure détaillée

Diagramme en cours de génération...

2.3 Exemple : Framework de parsing

interface Document {
  title: string;
  content: string;
}

interface Parser {
  parse(data: string): Document;
}

class JSONParser implements Parser {
  parse(data: string): Document {
    return JSON.parse(data);
  }
}

class XMLParser implements Parser {
  parse(data: string): Document {
    // Simulation XML parsing
    return { title: "XML Doc", content: data };
  }
}

abstract class DocumentProcessor {
  // Template Method
  process(data: string): void {
    const parser = this.createParser();
    const doc = parser.parse(data);
    this.validate(doc);
    this.save(doc);
  }

  // Factory Method
  protected abstract createParser(): Parser;

  private validate(doc: Document): void {
    if (!doc.title) throw new Error("Title required");
  }

  private save(doc: Document): void {
    console.log(`Saving document: ${doc.title}`);
  }
}

class JSONProcessor extends DocumentProcessor {
  protected createParser(): Parser {
    return new JSONParser();
  }
}

class XMLProcessor extends DocumentProcessor {
  protected createParser(): Parser {
    return new XMLParser();
  }
}

// Usage
const processor: DocumentProcessor = new JSONProcessor();
processor.process('{"title": "Hello", "content": "World"}');

2.4 Factory Method + Template Method (lien fort)

La Factory Method est presque toujours appelée dans une Template Method :

abstract class Application {
  // Template Method
  run(): void {
    this.init();
    const doc = this.createDocument(); // Factory Method
    doc.open();
    doc.save();
    this.cleanup();
  }

  protected abstract createDocument(): Document;
  protected init(): void { /* default */ }
  protected cleanup(): void { /* default */ }
}

2.5 Parametric Factory Method

Version où la factory method prend un paramètre :

abstract class PaymentService {
  protected abstract createPayment(type: string): Payment;

  pay(type: string, amount: number): void {
    const payment = this.createPayment(type);
    payment.process(amount);
  }
}

class DefaultPaymentService extends PaymentService {
  protected createPayment(type: string): Payment {
    switch (type) {
      case "credit": return new CreditCardPayment();
      case "paypal": return new PayPalPayment();
      default: throw new Error(`Unknown: ${type}`);
    }
  }
}

Partie 3 : Abstract Factory

3.1 Définition (GoF)

"Fournir une interface pour créer des familles d'objets apparentés ou interdépendants sans spécifier leurs classes concrètes."

3.2 Structure détaillée

Diagramme en cours de génération...

3.3 Exemple : Cross-platform UI (Java)

// Abstract Factory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// Concrete Factory 1
class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

// Concrete Factory 2
class MacOSFactory implements GUIFactory {
    public Button createButton() {
        return new MacOSButton();
    }
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

// Abstract Products
interface Button {
    void paint();
}

interface Checkbox {
    void paint();
}

// Concrete Products
class WindowsButton implements Button {
    public void paint() {
        System.out.println("Windows button");
    }
}

class MacOSButton implements Button {
    public void paint() {
        System.out.println("macOS button");
    }
}

// Client
class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        GUIFactory factory = new WindowsFactory();
        Application app = new Application(factory);
        app.paint();
    }
}

Partie 4 : Comparaison détaillée

Tableau comparatif

CritèreSimple FactoryFactory MethodAbstract Factory
NatureIdiomePattern GoFPattern GoF
MécanismeClasse statiqueHéritageComposition
ProduitsUn seul typeUn seul produitFamille de produits
ExtensionModifier la factoryNouvelle sous-classeNouvelle factory
OCPVioléRespectéRespecté
TestabilitéFaibleBonneBonne
ComplexitéTrès faibleMoyenneÉlevée

Quand utiliser quoi ?

Diagramme en cours de génération...

Anti-patterns associés

  • Factory Class : créer une factory pour chaque classe, même simple
  • Abstract Factory Overkill : utiliser Abstract Factory pour un seul produit
  • Static Factory as Service : utiliser Simple Factory pour des objets complexes avec dépendances

Partie 5 : Cas d'usage concrets

5.1 Système de documents

Diagramme en cours de génération...

5.2 Providers (cloud, paiement, auth)

interface CloudProvider {
  createCompute(): ComputeService;
  createStorage(): StorageService;
  createDatabase(): DatabaseService;
}

class AWSProvider implements CloudProvider {
  createCompute(): ComputeService { return new EC2(); }
  createStorage(): StorageService { return new S3(); }
  createDatabase(): DatabaseService { return new RDS(); }
}

class AzureProvider implements CloudProvider {
  createCompute(): ComputeService { return new VMs(); }
  createStorage(): StorageService { return new BlobStorage(); }
  createDatabase(): DatabaseService { return new SQLDatabase(); }
}

5.3 Query Builders

interface QueryBuilder {
  select(...fields: string[]): QueryBuilder;
  from(table: string): QueryBuilder;
  where(condition: string): QueryBuilder;
  getQuery(): string;
}

class MySQLQueryBuilder implements QueryBuilder {
  private query = "";

  select(...fields: string[]): QueryBuilder {
    this.query = `SELECT ${fields.join(", ")}`;
    return this;
  }

  from(table: string): QueryBuilder {
    this.query += ` FROM ${table}`;
    return this;
  }

  where(condition: string): QueryBuilder {
    this.query += ` WHERE ${condition}`;
    return this;
  }

  getQuery(): string {
    return this.query + ";";
  }
}

class PostgreSQLQueryBuilder implements QueryBuilder {
  // Similar with PostgreSQL-specific syntax
}

Résumé

  • Simple Factory : rapide, pratique, mais viole OCP
  • Factory Method : flexible via héritage, idéal pour frameworks
  • Abstract Factory : cohérence entre familles, idéal pour cross-platform
  • Le choix dépend du nombre de produits et du besoin d'extension