SOLDES de printemps

Fabrique abstraite

Alias : Abstract Factory

Intention

Fabrique abstraite est un patron de conception qui permet de créer des familles d’objets apparentés sans préciser leur classe concrète.

Patron de conception fabrique abstraite

Problème

Imaginons la création d’un simulateur pour un magasin de meubles. Votre code contient les classes suivantes :

  1. Une famille de produits appartenant à un même thème : Chaise + Sofa + TableBasse.

  2. Plusieurs variantes de cette famille. Par exemple, les produits Chaise + Sofa + TableBasse sont disponibles dans les variantes suivantes : Moderne, Victorien, ArtDéco.

Familles de produits et leurs variantes

Familles de produits et leurs variantes.

Vous devez trouver une solution pour créer des objets individuels (des meubles) et les faire correspondre à d’autres objets de la même famille. Les clients sont agacés lorsqu’ils reçoivent des meubles qui ne se marient pas.

Un sofa moderne ne se marie pas avec des chaises de style victorien.

De plus, vous n’avez pas envie de réécrire votre code chaque fois que vous ajoutez de nouvelles familles de produits à votre programme. Les vendeurs de meubles alimentent régulièrement leurs catalogues et il n’est pas envisageable de restructurer le code à chaque mise à jour.

Solution

La première chose que propose la fabrique abstraite est de déclarer explicitement des interfaces pour chaque produit de la famille de produits (dans notre cas : chaise, sofa, table basse). Toutes les autres variantes de produits peuvent ensuite se servir de ces interfaces. Par exemple, toutes les variantes de chaises peuvent implémenter l’interface Chaise ; toutes les variantes de tables basses peuvent implémenter TableBasse, etc.

La hiérarchie des classes chaise

Toutes les variantes du même objet peuvent être rangées dans la même hiérarchie de classe.

La prochaine étape est de déclarer la fabrique abstraite — une interface armée d’une liste de méthodes de création pour toutes les familles de produits (par exemple : créerChaise, créerSofa et créerTableBasse). Ces méthodes doivent renvoyer tous les types de produits abstraits des interfaces que nous avons créées précédemment : Chaise, Sofa, TableBasse, etc.

La hiérarchie des classes des _fabriques_

Chaque fabrique concrète correspond à une variante d’un produit spécifique.

Mais que deviennent les variantes des produits ? Pour chaque variante d’une famille de produits, nous créons une classe fabrique qui implémente l’interface FabriqueAbstraite. Une fabrique est une classe qui retourne un certain type de produits. Par exemple, la FabriqueMeubleModerne peut uniquement créer des ChaiseModerne, des SofaModerne et des TableBasseModerne.

Le code client travaille simultanément avec les interfaces abstraites respectives des fabriques et des produits. Nous pouvons ainsi changer le type de fabrique passé au code client et la variante de produit qu’il reçoit, sans avoir à le modifier.

Le client n’a pas besoin de connaître la classe concrète des fabriques qu’il utilise.

Imaginons un cas où le client désire une fabrique qui peut produire une chaise. Le client n’a pas à se préoccuper de la classe de la fabrique ni du type de chaise qu’il va obtenir. Il doit traiter les chaises de la même manière, que ce soit un modèle de style moderne ou victorien, en utilisant l’interface abstraite Chaise. Grâce à cette approche, le client ne sait qu’une seule chose à propos de la chaise, c’est qu’elle implémente la méthode sasseoir. De plus, quelle que soit la variante de la chaise renvoyée, elle correspondra systématiquement au type de sofa ou de table basse produit par le même objet fabrique.

Il ne reste plus qu’un point à éclaircir : si le client n’est lié qu’aux interfaces abstraites, comment les objets de la fabrique sont-ils créés ? En général, l’application crée un objet fabrique concret lors de l’initialisation. Mais avant cela, l’application doit choisir le type de la fabrique en fonction de la configuration ou des paramètres d’environnement.

Structure

Patron de conception fabrique abstraitePatron de conception fabrique abstraite
  1. Les Produits Abstraits déclarent une interface pour un ensemble d’objets distincts mais apparentés, qui forment une famille de produits.

  2. Les Produits Concrets sont des implémentations des produits abstraits groupés par variantes. Chaque produit abstrait (chaise/sofa) doit être implémenté dans toutes les variantes (victorien/moderne).

  3. L’interface Fabrique Abstraite déclare un ensemble de méthodes pour créer chacun des produits abstraits.

  4. Les Fabriques Concrètes implémentent les opérations de création d’objets de la fabrique abstraite. Chaque fabrique concrète correspond à une variante spécifique de produits et ne crée que ces variantes.

  5. Bien que les fabriques concrètes instancient des produits concrets, leurs méthodes de création ont une valeur de retour correspondant aux produits abstraits. Le code client qui sollicite une fabrique est ainsi isolé de la variante du produit obtenu. Le client peut travailler avec n’importe quelle variante de fabrique ou produit, tant qu’il interagit avec les interfaces abstraites.

Pseudo-code

Cet exemple montre comment la Fabrique abstraite peut être utilisée pour créer des éléments d’une UI multiplateforme sans coupler le code client avec les classes concrètes de l’UI, tout en gardant une cohérence entre les éléments créés et le système d’exploitation sélectionné.

Le diagramme de classe de l’exemple utilisé pour la fabrique abstraite

Exemple des classes d’une UI multiplateforme

Tous les éléments de l’UI d’une application multiplateforme sont censés avoir un comportement identique quel que soit le système d’exploitation, mais leur apparence peut légèrement varier. Vous devez faire en sorte que les éléments de l’interface correspondent bien au style du système d’exploitation. Vous ne voudriez pas vous retrouver avec des boutons macOS dans Windows.

L’interface de la fabrique abstraite déclare un ensemble de méthodes de création que le code client peut utiliser pour produire différents types d’éléments de l’UI. Chaque fabrique concrète correspond à un système d’exploitation particulier et crée ses propres éléments de l’UI en fonction de ce système d’exploitation.

Le fonctionnement est le suivant : lorsqu’une application est exécutée, elle vérifie le système d’exploitation utilisé et exploite cette information pour créer un objet de la fabrique qui y correspond. Cette fabrique est ensuite utilisée pour générer tout le reste des éléments de l’UI, ce qui évite les erreurs.

Grâce à cette approche, tant qu’il utilise leurs interfaces abstraites, le code client ne dépend plus des classes concrètes de la fabrique et des éléments de l’UI. Cela lui permet également d’exploiter les nouveaux éléments de l’UI ou les fabriques que vous pourriez ajouter dans le futur.

Nous n’avons ainsi plus besoin de modifier le code client à chaque nouvel élément de l’interface utilisateur que vous voulez ajouter dans votre application. Vous devrez simplement créer une nouvelle classe fabrique produisant ces éléments et apporter quelques modifications au code d’initialisation afin qu’il choisisse la classe appropriée.

// L’interface de la fabrique abstraite déclare un ensemble de
// méthodes qui retournent différents produits abstraits. Ces
// produits font partie de ce que l’on appelle une famille et
// sont reliés par un concept ou un thème de haut niveau. Les
// produits d’une famille sont généralement capables de
// collaborer les uns avec les autres. Une famille de produits
// peut avoir plusieurs variantes, mais les produits d’une
// variante ne sont pas compatibles avec les produits d’une
// autre.
interface GUIFactory is
    method createButton():Button
    method createCheckbox():Checkbox


// Les fabriques concrètes construisent une famille de produits
// qui appartiennent à une seule variante. La fabrique garantit
// la compatibilité des produits qui en sortent. Les signatures
// des méthodes des fabriques concrètes renvoient un produit
// abstrait, et à l’intérieur de la méthode, un produit concret
// est instancié.
class WinFactory implements GUIFactory is
    method createButton():Button is
        return new WinButton()
    method createCheckbox():Checkbox is
        return new WinCheckbox()

// Chaque fabrique concrète possède une variante de produit
// correspondante.
class MacFactory implements GUIFactory is
    method createButton():Button is
        return new MacButton()
    method createCheckbox():Checkbox is
        return new MacCheckbox()


// Chaque produit d’une famille de produits doit avoir une
// interface de base. Toutes les variantes du produit doivent
// implémenter cette interface.
interface Button is
    method paint()

// Les produits concrets sont créés par les fabriques concrètes
// correspondantes.
class WinButton implements Button is
    method paint() is
        // Affiche un bouton avec le style Windows.

class MacButton implements Button is
    method paint() is
        // Affiche un bouton avec le style macOS.

// Voici l’interface de base d’un autre produit. Tous les
// produits peuvent interagir les uns avec les autres, mais les
// interactions adéquates devraient être implémentées de
// préférence entre les produits d’une même variante concrète.
interface Checkbox is
    method paint()

class WinCheckbox implements Checkbox is
    method paint() is
        // Affiche une case à cocher avec le style Windows.

class MacCheckbox implements Checkbox is
    method paint() is
        // Affiche une case à cocher avec le style macOS.


// Le code client travaille uniquement avec les types abstraits
// des fabriques et des produits : FabriqueGUI, Bouton et
// CaseÀCocher. Ceci vous permet d’envoyer n’importe quelle
// sous-classe de fabrique ou de produit au code client sans
// toucher à son code.
class Application is
    private field factory: GUIFactory
    private field button: Button
    constructor Application(factory: GUIFactory) is
        this.factory = factory
    method createUI() is
        this.button = factory.createButton()
    method paint() is
        button.paint()


// L’application choisit le type de la fabrique en fonction de
// la configuration actuelle ou des paramètres d’environnement,
// et la crée à l’exécution (en général lors de la phase
// d’initialisation).
class ApplicationConfigurator is
    method main() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            factory = new WinFactory()
        else if (config.OS == "Mac") then
            factory = new MacFactory()
        else
            throw new Exception("Error! Unknown operating system.")

        Application app = new Application(factory)

Possibilités d’application

Utilisez la fabrique abstraite si votre code a besoin de manipuler des produits d’un même thème, mais que vous ne voulez pas qu’il dépende des classes concrètes de ces produits — soit vous ne les connaissez pas encore, soit vous voulez juste rendre votre code évolutif.

La fabrique abstraite fournit une interface qui permet de créer des objets pour chaque classe de la famille de produits. Tant que votre code utilise cette interface pour créer ses objets, il prendra systématiquement les bonnes variantes des produits disponibles dans votre application.

Étudiez la possibilité d’utiliser la fabrique abstraite lorsqu’une classe se retrouve avec un ensemble de patrons Fabrique qui occultent sa fonction principale.

Dans un programme bien conçu chaque classe n’a qu’une seule responsabilité. Lorsqu’une classe gère plusieurs types de produits, déplacer les méthodes fabrique dans des fabriques individuelles ou dans une implémentation à part entière d’une fabrique abstraite est souvent plus pratique.

Mise en œuvre

  1. Établissez une matrice de vos différents types de produits et leurs variantes.

  2. Déclarez des interfaces abstraites pour tous vos types de produits. Mettez en place vos produits concrets dans des classes qui implémentent ces interfaces.

  3. Déclarez une interface abstraite de la fabrique avec un ensemble de méthodes de création pour tous les produits abstraits.

  4. Implémentez une classe fabrique concrète pour chaque variante des produits.

  5. Insérez le code de l’initialisation de la fabrique quelque part dans votre application. Il doit instancier une des fabriques concrètes en fonction de la configuration de l’application ou de l’environnement d’exécution. Passez cette fabrique à toutes les classes qui construisent des produits.

  6. Parcourez votre code et repérez tous les appels aux constructeurs des produits. Remplacez-les par des appels à la méthode de création appropriée de la fabrique.

Avantages et inconvénients

  • Vous êtes assurés que les produits d’une fabrique sont compatibles entre eux.
  • Vous découplez le code client des produits concrets.
  • Principe de responsabilité unique. Vous pouvez déplacer tout le code de création des produits au même endroit, pour une meilleure maintenabilité.
  • Principe ouvert/fermé. Vous pouvez ajouter de nouvelles variantes de produits sans endommager l’existant.
  • Le code peut devenir plus complexe que nécessaire, car ce patron de conception impose l’ajout de nouvelles classes et interfaces.

Liens avec les autres patrons

  • La Fabrique est souvent utilisée dès le début de la conception (moins compliquée et plus personnalisée grâce aux sous-classes) et évolue vers la Fabrique abstraite, le Prototype, ou le Monteur (ce dernier étant plus flexible, mais plus compliqué).

  • Le Monteur se concentre sur la construction d’objets complexes étape par étape. La Fabrique abstraite se spécialise dans la création de familles d’objets associés. La fabrique abstraite retourne le produit immédiatement, alors que le monteur vous permet de lancer des étapes supplémentaires avant de récupérer le produit.

  • Les classes Fabrique abstraite sont souvent basées sur un ensemble de Fabriques, mais vous pouvez également utiliser le Prototype pour écrire leurs méthodes.

  • La Fabrique abstraite peut remplacer la Façade si vous voulez simplement cacher au code client la création des objets du sous-système.

  • Vous pouvez utiliser la Fabrique abstraite avec le Pont. Ce couple est très utile quand les abstractions définies par le pont ne fonctionnent qu’avec certaines implémentations spécifiques. Dans ce cas, la fabrique abstraite peut encapsuler ces relations et cacher la complexité au code client.

  • Les Fabriques abstraites, Monteurs et Prototypes peuvent tous être implémentés comme des Singletons.

Exemples de code

Fabrique abstraite en C# Fabrique abstraite en C++ Fabrique abstraite en Go Fabrique abstraite en Java Fabrique abstraite en PHP Fabrique abstraite en Python Fabrique abstraite en Ruby Fabrique abstraite en Rust Fabrique abstraite en Swift Fabrique abstraite en TypeScript

Informations supplémentaires