Accueil / Tutoriels / Abstract Factory

Abstract Factory

Publié le 23 Mai 2025
Conception

Abstract Factory est un pattern de création dont le rôle est de fournir une interface pour créer des familles d’objets liés entre eux sans spécifier leurs classes concrètes.

Principe

L’idée est d’avoir une interface ou classe abstraite qui définit plusieurs méthodes de création. Ensuite, chaque fabrique concrète implémente ces méthodes pour créer des objets de la même famille. Par exemple, nous avons une application qui fonctionne sur Windows et Mac avec deux familles de composants Button et CheckBox.

Chaque famille à son implémentation WindowsButton, MacButton, etc… Nous allons créer par exemple une interface GUIFactory avec des méthodes createButton() et createCheckBox(). Cette interface sera implémentée par deux classes comme WindowsFactory et MacFactory qui retournent chacune les versions Windows et Mac des composants.

Exemples d’utilisations

Génération d’éléments HTML selon le thème

Sur une application web, on peut choisir différents thèmes graphiques (clair/sombre). Chaque thème a ses propres composants et des comportements différents. Le rendu HTML/CSS doit être cohérent avec le thème choisi sans modifier le code métier.

Pour cela, on peut créer une fabrique abstraite qui définit les méthodes pour générer les composants et chaque fabrique concrète implémente les méthodes de la fabrique abstraite. Ainsi, le moteur de rendu peut générer dynamiquement l’interface utilisateur sans jamais connaître les détails du thème.

Intégration de services tiers selon l’environnement

Lors du développement d’une application e-commerce, on doit intégrer différents prestataires selon la thématique. Strype ou Paypal pour les paiements, ou encore d’autres prestataires pour l’envoi d'e-mails. L’utilisation d’une Abstract Factory permet de changer l’environnement (dev, prod, client 1, client 2) en injectant une fabrique différente sans avoir à modifier le code de l’application.

Points d’attentions

Le pattern Abstract Factory implique la création de plusieurs interfaces ou classes abstraites et autant de fabriques concrètes. Même pour des cas simples, on se retrouve avec une hiérarchie de classes lourdes.

Si l’on souhaite ajouter un nouveau type de composant, il faut modifier l’interface de notre Abstract Factory et mettre à jour toutes les fabriques (Factory) concrètes existantes pour gérer ce nouveau composant. La maintenance peut s’avérer fastidieuse.

Ce pattern pousse à créer des familles de produits étroitement liés qui doivent être utilisés ensemble ce qui limite la modularité.

Pour des usages simples, l’ajout d’une abstract factory peut s’avérer excessif. On risque d’alourdir un projet inutilement.

Implémenter une Abstract Factory

Pour une famille de classes, nous allons définir des interfaces ou des classes abstraites qui vont décrire ce que les objets peuvent faire sans dépendre d’une implémentation concrète.

On implémente ensuite des classes concrètes qui respectent l’interface définie précédemment.

Ensuite nous allons définir une interface (ou classe abstraite) pour notre Abstract Factory. Elle décrit une fonction de création d’objets pour notre famille. Cette Abstract Factory permet de créer tous les objets d’une même famille sans qu’on ait besoin d’instancier une classe à la main (avec new par exemple). Cette interface doit être ensuite implémentée par une Factory (notre classe concrète) qui contient la logique de création des objets de notre famille.

Voici un exemple en Go

On va utiliser le pattern Abstract Factory pour gérer une interface utilisateur qui peut changer en fonction du thème (mode clair/sombre).


package component

type Button interface {
   Render() string
}

type Input interface {
   Render() string
}

type LightButton struct{}

func (b LightButton) Render() string {
   return ``
}

type LightInput struct{}

func (i LightInput) Render() string {
   return ``
}

type DarkButton struct{}

func (b DarkButton) Render() string {
   return ``
}

type DarkInput struct{}

func (i DarkInput) Render() string {
   return ``
}


package component

type UIFactory interface {
   CreateButton() Button
   CreateInput() Input
}

type LightFactory struct{}

func (f LightFactory) CreateButton() Button {
   return LightButton{}
}

func (f LightFactory) CreateInput() Input {
   return LightInput{}
}

type DarkFactory struct{}

func (f DarkFactory) CreateButton() Button {
   return DarkButton{}
}

func (f DarkFactory) CreateInput() Input {
   return DarkInput{}
}


package main

import (
   "fmt"
   "designpatterns/abstract-factory/component"
)

func main() {
   var factory component.UIFactory

   // Choix du thème selon un paramètre fictif
   theme := "dark" // ou "light"

   if theme == "light" {
      factory = component.LightFactory{}
   } else {
      factory = component.DarkFactory{}
   }

   html := RenderPage(factory)
   fmt.Println("HTML généré :\n", html)
}

func RenderPage(factory component.UIFactory) string {
   button := factory.CreateButton()
   input := factory.CreateInput()

   return button.Render() + input.Render()
}

Le même exemple en PHP


namespace Practice\DesignPatterns\AbstractFactory;

interface ButtonInterface
{
   public function render(): string;
}


namespace Practice\DesignPatterns\AbstractFactory;

interface InputInterface
{
   public function render(): string;
}


namespace Practice\designPatterns\abstractFactory;

class DarkButton implements ButtonInterface
{
   public function render(): string {
       return '';
   }
}


namespace Practice\DesignPatterns\AbstractFactory;

class DarkInput implements InputInterface
{
   public function render(): string {
       return '';
   }
}


namespace Practice\DesignPatterns\AbstractFactory;

class LightButton implements ButtonInterface
{
   public function render(): string
   {
       return '';
   }
}


namespace Practice\DesignPatterns\AbstractFactory;

class LightInput implements InputInterface
{
   public function render(): string {
       return '';
   }
}


namespace Practice\DesignPatterns\AbstractFactory;

interface UIFactoryInterface
{
   public function createButton(): ButtonInterface;
   public function createInput(): InputInterface;
}


namespace Practice\DesignPatterns\AbstractFactory;

class DarkFactory implements UIFactoryInterface
{
   public function createButton(): ButtonInterface
   {
       return new DarkButton();
   }

   public function createInput(): InputInterface
   {
       return new DarkInput();
   }
}


namespace Practice\DesignPatterns\AbstractFactory;

class LightFactory implements UIFactoryInterface
{
   public function createButton(): ButtonInterface
   {
       return new LightButton();
   }

   public function createInput(): InputInterface
   {
       return new LightInput();
   }
}


declare(strict_types=1);

use Practice\DesignPatterns\AbstractFactory\DarkFactory;
use Practice\DesignPatterns\AbstractFactory\LightFactory;
use Practice\DesignPatterns\AbstractFactory\UIFactoryInterface;

require "./vendor/autoload.php";

function renderPage(UIFactoryInterface $factory): string {
   $button = $factory->createButton();
   $input = $factory->createInput();
   return $button->render() . "\n" . $input->render();
}

// Simuler un choix de thème (par exemple via GET)
$theme = $_GET['theme'] ?? 'dark';

if ($theme === 'light') {
   $factory = new LightFactory();
} else {
   $factory = new DarkFactory();
}

echo "";
echo renderPage($factory);
echo "";

Conclusion

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Abstract Factory. Vous pouvez retrouver mon article sur les patrons de conception ou les autres tutoriels sur Bridge, Mediator, Decorator, Chain of responsibility et Prototype.