Design pattern: Façade

Objectif:

Simplifie l’interface d’une ou plusieurs classes

Justification

Problème

Lorsqu’une interface doit être consommée par une classe cliente, il est courant de vouloir simplifier cette interface:
– pour cacher la complexité de l’implémentation interne et présenter une interface simple à utiliser,
– simplifier l’appel à beaucoup d’objets internes en ne proposant qu’une interface unique,
– limiter les dépendances des classes clientes en évitant d’exposer trop d’objets internes. Ces objets internes étant invisibles de l’extérieur, on est sûr qu’ils sont utilisés uniquement en interne. On est alors plus libre de les modifier pour des éventuelles évolutions futures sans casser les dépendances des classes clientes extérieures.

Par exemple
On réalise un système d’émission de billets qui utilise d’autres systèmes de billeterie hétérogènes:
– un système de réservation de billets d’avion accessible au moyen d’une API propriétaire,
– un système de réservation de billets de train interrogeable avec un web service sécurisé,

Les demandes d’émissions peuvent être lancées au moyen d’une interface graphique ou d’un web service. Pour lancer les émissions, on ne communique que les références de dossier. C’est au système de récupérer toutes les données nécessaires pour effectuer les émissions.

Sachant que le système peut être interrogée au moins de 2 façons: interface graphique et web service, on voudrait avoir une interface unique pour lancer l’émission:

public interface ITicketIssuingSystem
{
    TicketIssue IssueTicket(string ticketReference);
    bool IsTicketAlreadyIssued(string ticketReference);
}

public class TicketIssue
{
    public bool HasBeenIssued { get; set; }
    public string OccuredError { get; set; }
}

L’interface de l’objet interne permettant de s’interfacer avec le système de réservation de billets d’avion est:

public interface IAirlineTicketBookingSystem
{
    ...
    ITicketData GetTicket(string ticketReference);
}

L’interface de l’objet interne permettant de s’interfacer avec le système de réservation de billets de train est:

public interface ITrainTicketBookingSystem
{
    ...
    ITicketData GetTicket(string ticketReference);
}

Pour interroger la base de données, on utilise la classe "TicketRepository" qui effectue la requête sur la table "IssuedTicket":

internal class IssuedTicketRepository
{
    ...
    public bool AddNewIssuedTicket(string ticketReference)
    {...}

    public bool ContainsTicket(string ticketReference)
    {
        using (var context = new TicketDBEntities())
        {
            return context.IssuedTickets.Any(t => t.Reference.Equals(ticketReference));
        };
    }
}

Pour effectuer chaque émission, le système doit:
– interroger les deux autres systèmes de réservation pour récupérer les données relatives au billet,
– interroger une base de données pour vérifier que l’émission du billet n’a pas déjà été demandée, de façon à ne pas émettre 2 fois le même billet,
– logger des informations relatives à la requête,
– envoyer des mails aux passagers pour indiquer l’émission du ou des billets.

Solution

"Façade" permet d’apporter une solution:
– en proposant une interface unique aux classes clientes,
– en évitant d’exposer les autres objets internes aux classes clientes et
– en utilisant les objets internes pour effectuer l’émission et renvoyer les résultats.

La "Façade" sera alors l’unique objet appelé par l’interface graphique et par le web service et c’est elle qui va interroger tous les autres objets pour effectuer l’émission:

public class TicketIssueFacade : ITicketIssuingSystem
{
    private IAirlineTicketBookingSystem _airlineSystem;
    private ITrainTicketBookingSystem _trainSystem;
    private IssuedTicketRepository _issuedTicketRepository;
    private ILog _logger;

    public TicketIssueFacade(IAirlineTicketBookingSystem airlineSystem, 
        ITrainTicketBookingSystem trainSystem, IssuedTicketRepository _issuedTicketRepository,
        ILog logger)
    {
        this._airlineSystem = airlineSystem;
        this._trainSystem = trainSystem;
        this._issuedTicketRepository = issuedTicketRepository;
        this._logger = logger;
    }

    public TicketIssue IssueTicket(string ticketReference)
    {
        if (this.IsTicketAlreadyIssued(ticketReference))
            return new TicketIssue
            {
                HasBeenIssued = false,
                OccuredError = "Ticket has been alreadu issued."
            };

        var ticket = this._airlineSystem.GetTicket(ticketReference);
        if (ticket == null)
        {
            ticket = this._trainSystem.GetTicket(ticketReference);
        }

        if (ticket == null)
            return new TicketIssue
            {
                HasBeenIssued = false,
                OccuredError = "Ticket data not found."
            };

        bool ticketIssued = false;
        string occuredError = string.Empty;
        if (this._issuedTicketRepository.AddNewIssuedTicket(ticketReference))
        {
            ticketIssued = true;
            this._logger.InfoFormat("Ticket {0} has been issued.", ticketReference);
        }
        else
        {
            occuredError = "Ticket not issued for an unknown reason."
            this._logger.InfoFormat("Ticket {0} not issued.", ticketReference);
        }

        return new TicketIssue
        {
            HasBeenIssued = ticketIssued,
            OccuredError = occuredError,
        };
    }

    public bool IsTicketAlreadyIssued(string ticketReference)
    {
        return this._issuedTicketRepository.ContainsTicket(ticketReference);
    }
}

TicketIssueFacade devient alors la seule classe accessible par des classes clientes permettant d’émettre des billets.

Pour aller plus loin…

Diagramme théorique

Limites

Le plus gros inconvénient à "Façade" est qu’une classe façade peut rapidement devenir une classe "fourre-tout" où on aura tendance à placer tout le code. La raison principale est que c’est la classe qui met en relation d’autres classes sous-jacentes. Le risque est d’avoir une façade contenant beaucoup de code métier. Elle va donc perdre son objectif de simplifier des interfaces internes au profit d’une classe mettant tous les objets internes en relation. Cette tendance sera renforcée si la façade est consommée par d’autres objets internes.
"Façade" ne peut donc suffire seul à organiser l’implémentation, il faut y ajouter une rigueur et garder en tête que la façade sert à simplifier une implémentation ou des interfaces internes et doit être consommée par des objets externes.

Différences entre "Adapter" et "Façade"

"Adapter" et "Façade" sont très semblables, ils visent tous deux à adapter une complexité à une classe cliente.
Toutefois, "Adapter" s’utilise dans un contexte plus précis de l’adaptation ou de la conversion d’une ou plusieurs classes pour un besoin particulier.
"Façade" s’utilisera davantage pour cacher la complexité d’une fonctionnalité plus générale c’est-à-dire simplifier une implémentation ou plus généralement une interface, en particulier lorsque plusieurs classes internes doivent être appelées.

"Façade" simplifie les interfaces d’une ou plusieurs classes alors que "Adapter" convertit des interfaces pré-existantes.

Leave a Reply