Aller au contenu

Façade

Design Pattern Facade

Un point d’entrée unique vers un système complexe

Introduction

Le design pattern Facade est un pattern de conception structurel dont l’objectif est de fournir un point d’entrée simple et unifié vers un ensemble de classes, de services ou de "sous-systèmes" plus complexes.

Au lieu d'interagir directement avec les différents composants, le code s'appuie sur un point d’entrée unique proposé par la façade, qui centralise les interactions et masque la complexité de leur fonctionnement. Le "code client" utilisant la façade devient ainsi plus lisible et moins fortement couplé aux services qu'il utilise.

Ce design pattern est particulièrement adapté lorsqu'un traitement nécessite de coordonner l'exécution de plusieurs services ou l'utilisation de composants.

Problématique

Imaginons une application e-commerce devant traiter une commande. Pour qu'une commande soit validée, plusieurs opérations doivent être réalisées :

  • Vérifier la disponibilité des produits

  • Débiter le paiement

  • Réserver le stock

  • Générer la facture

  • Envoyer un e-mail de confirmation

Sans utiliser le design pattern Facade, le code traitant les commandes doit connaître tous les services, qu'il appelle séquentiellement :

inventoryService.CheckAvailability(order);

paymentService.Process(order);

stockService.Reserve(order);

invoiceService.Generate(order);

notificationService.SendConfirmation(order);

Ce code présente un inconvénient : un couplage fort entre la classe traitant la commande ("code client") et les services.

Fonctionnement

Le design pattern Facade consiste à créer une classe qui orchestre l'exécution des différents services. Ainsi, notre code interagit avec cette façade et se contente d’appeler une méthode permettant de traiter la commande :

orderFacade.Process(order);

La façade se charge ensuite d'appeler les différents services de manière ordonnée.

Conception

Voici le schéma de conception mettant en évidence la façade nommée OrderFacade qui prend en charge le traitement d'une commande :

Design Pattern Façade : conception

Pour simplifier ce diagramme de classes, la classe OrderFacade référence directement les classes de services et non les interfaces. Dans l'implémentation présentée ci-dessous, les interfaces sont présentes et utilisées.

Implémentation

Les services

Voici les classes réalisant les traitements sur une commande :

// **************************************************
// ** Interfaces
// **************************************************
public interface IInventoryService
{
    void CheckAvailability(Order order);
}

public interface IPaymentService
{
    void Process(Order order);
}

public interface IStockService
{
    void Reserve(Order order);
}

public interface IInvoiceService
{
    void Generate(Order order);
}

public interface INotificationService
{
    void SendConfirmation(Order order);
}

// **************************************************
// ** Implémentations
// **************************************************
public class InventoryService : IInventoryService
{
    public void CheckAvailability(Order order)
        => Console.WriteLine("Vérification du stock");
}

public class PaymentService : IPaymentService
{
    public void Process(Order order)
        => Console.WriteLine("Paiement effectué");
}

public class StockService : IStockService
{
    public void Reserve(Order order)
        => Console.WriteLine("Stock réservé");
}

public class InvoiceService : IInvoiceService
{
    public void Generate(Order order)
        => Console.WriteLine("Facture générée");
}

public class NotificationService : INotificationService
{
    public void SendConfirmation(Order order)
        => Console.WriteLine("Confirmation envoyée");
}

La façade

Voici notre façade qui orchestre le traitement des commandes :

public interface IOrderFacade
{
    void Process(Order order);
}

public class OrderFacade : IOrderFacade
{
    private readonly IInventoryService _inventoryService;
    private readonly IPaymentService _paymentService;
    private readonly IStockService _stockService;
    private readonly IInvoiceService _invoiceService;
    private readonly INotificationService _notificationService;

    public OrderFacade(
        IInventoryService inventoryService,
        IPaymentService paymentService,
        IStockService stockService,
        IInvoiceService invoiceService,
        INotificationService notificationService)
    {
        _inventoryService = inventoryService;
        _paymentService = paymentService;
        _stockService = stockService;
        _invoiceService = invoiceService;
        _notificationService = notificationService;
    }

    public void Process(Order order)
    {
        _inventoryService.CheckAvailability(order);
        _paymentService.Process(order);
        _stockService.Reserve(order);
        _invoiceService.Generate(order);
        _notificationService.SendConfirmation(order);
    }
}

Utilisation

Notre code peut alors instancier la façade et demander le traitement d'une commande :

var facade = new OrderFacade(
    new InventoryService(),
    new PaymentService(),
    new StockService(),
    new InvoiceService(),
    new NotificationService());

facade.Process(order);

Avec l'injection de dépendances

L'utilisation de l'injection de dépendances permet de mettre à disposition de la façade les objets exécutant les services, sans qu'elle ait à les instancier :

services.AddScoped<IInventoryService, InventoryService>();
services.AddScoped<IPaymentService, PaymentService>();
services.AddScoped<IStockService, StockService>();
services.AddScoped<IInvoiceService, InvoiceService>();
services.AddScoped<INotificationService, NotificationService>();

services.AddScoped<IOrderFacade, OrderFacade>();

L'implémentation de la classe OrderFacade ne change pas : ses dépendances sont déjà reçues par l’intermédiaire de son constructeur. Le conteneur d’injection de dépendances se charge désormais de les créer et de les lui fournir.

Puis nous injectons notre façade dans le "code-client", implémenté par un contrôleur :

public class OrderController : ControllerBase
{
    private readonly IOrderFacade _orderFacade;

    public OrderController(IOrderFacade orderFacade)
    {
        _orderFacade = orderFacade;
    }

    public void Validate(Order order)
    {
        _orderFacade.Process(order);
    }
}

Avantages et limites

Les avantages de l'utilisation de ce pattern sont les suivants :

  • Simplifier l'utilisation d'un processus complexe.

  • Réduire le couplage entre le client et les composants.

  • Rendre le code plus lisible.

Ainsi, d'un point de vue de la conception, le pattern Facade contribue au principe SOLID OCP (Open/Closed Principle) en protégeant le "code client" des évolutions des services sous-jacents (traitant la commande). Le fonctionnement interne de la façade peut évoluer sans modifier le code qui l’utilise. Cependant, la façade elle-même doit généralement être modifiée lorsqu’une nouvelle étape est ajoutée. Ainsi, bien qu'il y contribue, le pattern ne garantit pas à lui seul, l’application complète de ce principe SOLID.

Toutefois, attention, une façade peut devenir trop volumineuse si elle concentre un nombre excessif de responsabilités. Il est donc important de conserver une API cohérente et de créer plusieurs façades lorsque le "sous-système" couvre plusieurs domaines fonctionnels distincts.