SOLDES de printemps

Procuration

Alias : Proxy

Intention

La Procuration est un patron de conception structurel qui vous permet d’utiliser un substitut pour un objet. Elle donne le contrôle sur l’objet original, vous permettant d’effectuer des manipulations avant ou après que la demande ne lui parvienne.

Patron de conception procuration

Problème

Pourquoi vouloir maîtriser l’accès à un objet ? Voici un exemple : vous avez un énorme objet qui consomme beaucoup de ressources, mais vous n’en avez besoin que de temps en temps.

Problème résolu grâce au patron de conception procuration

Les requêtes sur la base de données sont parfois très lentes.

Vous pouvez recourir à l’instanciation paresseuse : créer l’objet uniquement lorsque vous en avez besoin. Vous devrez implémenter une initialisation différée dans tous les clients pour l’objet concerné. Malheureusement, vous allez vous retrouver avec beaucoup de code dupliqué.

Dans le meilleur des mondes, nous voudrions mettre ce code directement dans la classe de l’objet, mais ce n’est pas toujours possible. La classe pourrait par exemple faire partie d’une application externe non modifiable.

Solution

Ce patron de conception vous propose de créer une classe procuration qui a la même interface que l’objet du service original. Vous passez ensuite l’objet procuration à tous les clients de l’objet original. Lors de la réception d’une demande d’un client, la procuration crée l’objet du service original et lui délègue la tâche.

Solution proposée par le patron de conception procuration

La procuration se déguise en objet base de données. Elle peut gérer l’instanciation paresseuse et mettre en cache le résultat sans que le client ou que l’objet de la base de données ne le remarque.

Mais quel est son intérêt ? Si vous voulez lancer un traitement avant ou après avoir appliqué la logique principale de la classe, la procuration peut intervenir sans modifier cette dernière. Elle implémente la même interface que la classe originale, et peut donc être passée en paramètre à n’importe quel client qui attend l’objet du service original.

Analogie

Une carte de crédit est une procuration pour une liasse de billets

Une carte de crédit peut être utilisée au même titre que de l’argent liquide.

Une carte de crédit est une procuration pour un compte bancaire, qui est une procuration pour une liasse de billets. Ils implémentent la même interface : tous deux peuvent être utilisés pour effectuer un paiement. Le consommateur apprécie grandement, car il n’a pas besoin de garder une grosse somme en liquide sur lui. Le commerçant est également très heureux, car les transactions sont versées électroniquement vers son compte en banque, sans courir le risque d’égarer son dépôt ou de se le faire voler sur le chemin de la banque.

Structure

Structure du patron de conception procurationStructure du patron de conception procuration
  1. L’Interface Service déclare l’interface du service. La procuration doit implémenter cette interface afin de pouvoir se déguiser en objet du service.

  2. Le Service est une classe qui fournit la logique métier dont vous voulez vous servir.

  3. La Procuration est une classe dotée d’un attribut qui pointe vers un objet service. Une fois que la procuration a lancé tous ses traitements (instanciation paresseuse, historisation des logs, vérification des droits, mise en cache, etc.), elle envoie la demande à l’objet du service.

    En général, les procurations gèrent le cycle de vie de leurs objets service.

  4. Le Client passe par la même interface pour travailler avec les services et les procurations. Il est ainsi possible de passer une procuration à n’importe quel code qui attend un objet service.

Pseudo-code

L’exemple suivant montre comment le patron de conception Procuration nous aide à utiliser l’instanciation paresseuse et la mise en cache pour intégrer une librairie YouTube externe.

Structure de l’exemple utilisé pour la procuration

Résultats obtenus pour la mise en cache d’un service à l’aide d’une procuration.

La librairie nous fournit une classe pour télécharger des vidéos. Malheureusement, elle n’est pas très efficace. Si l’application client demande une même vidéo à plusieurs reprises, la librairie va la télécharger encore et encore, plutôt que de la mettre en cache après la première utilisation.

La classe procuration implémente la même interface que l’outil de téléchargement original et délègue tout le travail à ce dernier. En revanche, elle garde la trace de tous les fichiers téléchargés et retourne les données du cache lorsque l’application fait plusieurs fois appel à la même vidéo.

// L’interface d’un service distant
interface ThirdPartyYouTubeLib is
    method listVideos()
    method getVideoInfo(id)
    method downloadVideo(id)

// La méthode d’implémentation concrète d’un connecteur de
// service. Les méthodes de cette classe peuvent demander des
// informations à YouTube. La vitesse de la demande dépend de la
// connexion Internet de l’utilisateur, ainsi que de celle de
// YouTube. L’application sera plus lente si plusieurs demandes
// sont envoyées en même temps, même si elles recherchent les
// mêmes informations.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
    method listVideos() is
        // Envoie une demande via une API à YouTube.

    method getVideoInfo(id) is
        // Récupère les métadonnées d’une vidéo.

    method downloadVideo(id) is
        // Télécharge un fichier vidéo sur YouTube.

// Pour économiser de la bande passante, nous pouvons mettre en
// cache les résultats des demandes et les mettre de côté
// temporairement. Mais vous n’allez pas forcément pouvoir
// écrire ce code directement dans la classe du service. Elle
// pourrait provenir d’une librairie externe ou avoir été
// définie comme `final`. Pour cette raison, nous écrivons le
// code de la mise en cache dans une nouvelle classe procuration
// qui implémente la même interface que la classe du service.
// Elle délègue les tâches à l’objet du service lorsque les
// demandes doivent vraiment être envoyées.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
    private field service: ThirdPartyYouTubeLib
    private field listCache, videoCache
    field needReset

    constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is
        this.service = service

    method listVideos() is
        if (listCache == null || needReset)
            listCache = service.listVideos()
        return listCache

    method getVideoInfo(id) is
        if (videoCache == null || needReset)
            videoCache = service.getVideoInfo(id)
        return videoCache

    method downloadVideo(id) is
        if (!downloadExists(id) || needReset)
            service.downloadVideo(id)

// La classe GUI qui fonctionnait auparavant avec un objet
// Service n’a pas besoin d’être modifiée tant qu’elle interagit
// avec lui par le biais d’une interface. Nous pouvons passer en
// toute sécurité un objet procuration à la place d’un objet du
// service, car ils implémentent la même interface.
class YouTubeManager is
    protected field service: ThirdPartyYouTubeLib

    constructor YouTubeManager(service: ThirdPartyYouTubeLib) is
        this.service = service

    method renderVideoPage(id) is
        info = service.getVideoInfo(id)
        // Affiche la page de la vidéo.

    method renderListPanel() is
        list = service.listVideos()
        // Affiche la liste des miniatures des vidéos.

    method reactOnUserInput() is
        renderVideoPage()
        renderListPanel()

// L’application peut configurer les procurations à la volée.
class Application is
    method init() is
        aYouTubeService = new ThirdPartyYouTubeClass()
        aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
        manager = new YouTubeManager(aYouTubeProxy)
        manager.reactOnUserInput()

Possibilités d’application

La procuration possède de nombreux usages. Passons en revue les plus populaires.

Instanciation paresseuse (procuration virtuelle). À utiliser lorsque l’objet du service est très consommateur en ressources système car il est actif en permanence, mais vous n’en avez pas tout le temps besoin.

Vous pouvez différer l’initialisation de l’objet plutôt que de le créer au lancement de l’application.

Vérification des droits (procuration de protection). Si vous avez besoin de limiter l’accès de vos clients à l’objet du service, par exemple si vos objets sont des parties cruciales d’un système d’exploitation et que les clients sont différentes applications lancées (dont certaines sont malveillantes).

La procuration peut ne transmettre une demande à l’objet du service que si les identifiants du client remplissent certains critères.

Lancement local d’un service distant (procuration à distance). L’objet du service se situe sur un serveur distant.

Dans ce cas, la procuration envoie la demande par le réseau en s’occupant de gérer tous les détails compliqués de la gestion du réseau.

Demande de logs (procuration de logs). Pour garder un historique des demandes passées auprès de l’objet du service.

La procuration peut garder la trace de toutes les demandes passées au service.

Mettre en cache les résultats des demandes (procuration de cache). Si vous voulez mettre en cache les résultats des demandes faites au client et gérer le cycle de vie de ce cache, principalement lorsque les résultats sont imposants.

La procuration peut gérer la mise en cache des demandes récurrentes qui retournent toujours le même résultat. Les paramètres des demandes peuvent servir de clé.

Référencement intelligent. Si vous voulez vous débarrasser d’un objet très consommateur en ressources lorsqu’aucun client ne l’utilise.

La procuration peut garder la trace des clients qui ont récupéré une référence vers l’objet du service ou vers ses résultats. De temps en temps, la procuration peut faire le tour des clients et vérifier s’ils sont toujours actifs. Si la liste des clients est vide, la procuration peut supprimer l’objet du service afin de libérer des ressources.

Elle peut également savoir si le client avait modifié l’objet du service. Les objets non modifiés peuvent ensuite être réutilisés par les autres clients.

Mise en œuvre

  1. Si le service n’a pas encore d’interface, créez-en une pour rendre la procuration et les objets du service interchangeables. Il n’est pas toujours possible d’extraire l’interface de la classe du service, car vous devez effectuer des modifications pour que tous les clients du service utilisent cette interface. Heureusement, nous avons un plan B. Transformez la procuration en sous-classe de la classe service, afin qu’elle hérite de l’interface du service.

  2. Créez la classe procuration. Elle doit inclure un attribut qui stocke la référence au service. En général, les procurations créent et gèrent le cycle de vie de leurs services. En de rares occasions, un service est passé à la procuration par le biais d’un constructeur du client.

  3. Mettez en place les méthodes de la procuration et leur fonctionnement. Dans la plupart des cas, la procuration doit déléguer le travail à l’objet du service après avoir lancé ses traitements.

  4. Réfléchissez à l’implémentation d’une méthode de création qui décide si votre client doit utiliser directement le service ou passer par la procuration. Il peut s’agir d’une méthode statique toute simple ou d’une classe procuration avec une méthode fabrique complète.

  5. Envisagez également l’implémentation d’une instanciation paresseuse pour l’objet du service.

Avantages et inconvénients

  • Vous pouvez contrôler l’objet du service sans que le client ne s’en aperçoive.
  • Vous pouvez gérer le cycle de vie de l’objet du service si les clients ne s’en occupent pas.
  • La procuration fonctionne même si l’objet du service n’est pas prêt ou pas disponible.
  • Principe ouvert/fermé. Vous pouvez ajouter de nouvelles procurations sans toucher au service ou aux clients.
  • Le code peut devenir plus complexe puisque vous devez y introduire de nombreuses classes.
  • La réponse du service peut mettre plus de temps à arriver.

Liens avec les autres patrons

  • Avec Adaptateur, vous accédez à un objet existant via une interface différente. Avec Procuration, l’interface reste la même. Avec Décorateur, vous accédez à l’objet via une interface améliorée.

  • La Façade et la Procuration ont une similarité : ils mettent en tampon une entité complexe et l’initialisent individuellement. Contrairement à la façade, la procuration implémente la même interface que son objet service, ce qui les rend interchangeables.

  • Le Décorateur et la Procuration ont des structures similaires, mais des intentions différentes. Ces deux patrons sont bâtis sur le principe de la composition, où un objet est censé déléguer certains traitements à un autre. La différence est qu’en principe, la procuration gère elle-même le cycle de vie de son objet service, alors que la composition des décorateurs est toujours contrôlée par le client.

Exemples de code

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