Aller au contenu

Design Pattern Abstract Factory

Créer des ensemble d’objets liés sans dépendre de leurs classes

Introduction

Le design pattern Abstract Factory est un pattern de conception de création dont l'objectif est de fournir une interface, permettant de créer des familles d'objets, sans que le code client dépende des classes ayant permis d'instancier ces objets. Lorsqu'une application doit créer plusieurs objets dont les classes varient selon un même contexte, instancier directement ces classes avec l'opérateur new introduit un couplage fort.

Le design pattern Abstract Factory permet de déplacer cette responsabilité vers des fabriques spécialisées. Le code client manipule uniquement des abstractions et peut ainsi utiliser différentes familles d'objets sans connaître les classes ayant permis d'instancier les classes.

Problématique

Nous devons développer une application .NET capable d'afficher une IHM sur deux systèmes d'exploitation : Windows et macOS. Elle utilise des boutons et des boîtes de dialogue pour afficher du contenu et interagir avec les utilisateurs. Voici quelques classes pour chaque système d'exploitation :

Familles de classes Windows et macOs

Sans utiliser de design pattern, dans le code client, nous pourrions directement déterminer les classes à instancier en fonction du système d'exploitation :

if (platform == Platform.Windows)
{
    var button = new WindowsButton();
    var dialog = new WindowsDialog();
}
else if (platform == Platform.MacOS)
{
    var button = new MacOSButton();
    var dialog = new MacOSDialog();
}

Toutefois, cette implémentation a des inconvénients. Le code client connaît les classes qui sont instanciées. L'ajout d'un nouveau système d'exploitation implique de modifier ce code. De plus, rien n'empêche de créer accidentellement une combinaison incohérente pour un système d'exploitation :

var button = new WindowsButton();
var dialog = new MacOSDialog();

Les composants créés appartiennent à des familles : un bouton Windows doit être utilisé avec une boîte de dialogue Windows. Le design pattern Abstract Factory permet de regrouper la création de ces familles d'objets.

Conception

La structure du pattern Abstract Factory peut être représentée par ce diagramme de classes :

Design Pattern Abstract Factory : conception

Le design pattern Abstract Factory repose sur quatre catégories d'éléments :

  • Les produits abstraits qui définissent les interfaces des objets à créer

  • Les produits concrets qui correspondent aux différentes implémentations

  • L'Abstract Factory qui définit les méthodes permettant de créer chaque type de produit

  • Les Concrete Factories qui créent les produits appartenant à une même famille

Dans notre exemple, nous avons :

  • Deux produits abstraits : les interfaces IButton et IDialog.

  • Deux familles de produits : Windows (qui regroupe les classes WindowsButton et WindowsDialog) et macOS (qui regroupe les classes MacOSButton et MacOSDialog), où chaque famille possède sa propre fabrique, et chaque fabrique implémente une interface commune nommée IUiFactory :

Classes Factory

Implémentation des produits abstraits

Commençons par définir les interfaces des deux composants graphiques (le bouton et la boîte de dialogue) :

public interface IButton
{
    void Render();
}

public interface IDialog
{
    void Show();
}

Ces interfaces permettent au code client d'utiliser les composants sans connaître les classes qui les implémentent.

Implémentation des produits Windows

Voici l'implémentation des classes représentant les composants spécifiques à Windows :

public class WindowsButton : IButton
{
    public void Render()
    {
        Console.WriteLine("Affichage d'un bouton pour Windows");
    }
}

public class WindowsDialog : IDialog
{
    public void Show()
    {
        Console.WriteLine("Affichage d'une boîte de dialogue pour Windows");
    }
}

Ces deux classes constituent une première famille de produits.

Implémentation des produits macOS

Implémentons une seconde famille destinée à macOS :

public class MacOSButton : IButton
{
    public void Render()
    {
        Console.WriteLine("Affichage d'un bouton pour macOS");
    }
}

public class MacOSDialog : IDialog
{
    public void Show()
    {
        Console.WriteLine("Affichage d'une boîte de dialogue pour macOS");
    }
}

Les classes WindowsButton et MacOSButton implémentent donc la même interface (IButton), tout comme les classes WindowsDialog et MacOSDialog (IDialog).

Implémentation de l'Abstract Factory

Voici l'interface représentant notre fabrique abstraite :

public interface IUiFactory
{
    IButton CreateButton();

    IDialog CreateDialog();
}

Cette interface n'utilise pas les classes qui seront instanciées : une fabrique fournit un bouton et une boîte de dialogue via leur interface respective.

Implémentation des classes Concrete Factories

Chaque plateforme possède ensuite sa propre implémentation de l'interface IUiFactory.

Pour le système d'exploitation Windows :

public class WindowsUiFactory : IUiFactory
{
    public IButton CreateButton()
        => new WindowsButton();

    public IDialog CreateDialog()
        => new WindowsDialog();
}

Et pour le système d'exploitation macOS :

public class MacOSUiFactory : IUiFactory
{
    public IButton CreateButton()
        => new MacOSButton();

    public IDialog CreateDialog()
        => new MacOSDialog();
}

Chaque fabrique crée ainsi une famille cohérente de produits.

Utilisation

Le code client peut désormais dépendre uniquement de l'interface IUiFactory pour créer des boutons et boîtes de dialogue :

public class Application
{
    private IButton Button { get; set; }
    private IDialog Dialog { get; set; }

    public Application(IUiFactory factory)
    {
        Button = factory.CreateButton();
        Dialog = factory.CreateDialog();
    }

    public void Render()
    {
        Button.Render();
        Dialog.Show();
    }
}

Le code de la classe Application ne connaît pas les classes utilisées pour créer des composants. La famille de composants utilisée est déterminée par la fabrique fournie lors de l'instanciation de la classe Application :

var factory = new WindowsUiFactory();

var application = new Application(factory);

application.Render();

Pour utiliser les composants macOS, il suffit d'instancier une autre fabrique :

var factory = new MacOSUiFactory();

var application = new Application(factory);

application.Render();

Le code de la classe Application n'est pas modifié : seule l'implémentation de la fabrique qui lui est fournie est différente.

Avantages

Le principal avantage du design pattern Abstract Factory est de découpler le code client des classes concrètes utilisées pour créer les objets, puis de garantir une cohérence entre les produits d'une même famille. La factory WindowsUiFactory fournit uniquement des composants Windows et la factory MacOSUiFactory uniquement des composants macOS.

L'ajout d'une nouvelle famille est également relativement simple. Par exemple, pour prendre en charge le système d'exploitation Linux, nous pourrions ajouter :

Classes pour le système d'exploitation Linux

sans apporter de modifications à la classe Application.

Aussi, le design pattern Abstract Factory favorise le respect du principe SOLID Open/Closed (OCP) : notre solution peut être étendue avec de nouvelles familles sans modifier le code client existant. Il favorise également le principe SOLID Dependency Inversion (DIP), puisque le code client dépend des abstractions IUiFactory, IButton et IDialog plutôt que des classes.

Design Pattern Abstract Factory ou Factory Method ?

Les design patterns Abstract Factory et Factory Method répondent tous les deux à un objectif de découplage de la création d'objets, mais ils ne traitent pas exactement la même problématique.

Le design pattern Factory Method permet de déléguer la création d'un type d'objet à des classes spécialisées.

Le design pattern Abstract Factory permet de créer plusieurs types d'objets appartenant à une même famille. Dans notre exemple, une fabrique ne crée pas uniquement un bouton : elle fournit l'ensemble des composants associés à une plateforme.

Ainsi, Factory Method s'intéresse principalement à la création d'un objet, tandis qu'Abstract Factory organise la création d'une famille d'objets.