CodeGym /Blog Java /Random-FR /Modèle de conception de proxy
Auteur
Artem Divertitto
Senior Android Developer at United Tech

Modèle de conception de proxy

Publié dans le groupe Random-FR
En programmation, il est important de planifier correctement l'architecture de votre application. Les modèles de conception sont un moyen indispensable pour y parvenir. Aujourd'hui, parlons des proxys.

Pourquoi avez-vous besoin d'un proxy ?

Ce modèle aide à résoudre les problèmes associés à l'accès contrôlé à un objet. Vous pouvez demander : "Pourquoi avons-nous besoin d'un accès contrôlé ?" Examinons quelques situations qui vous aideront à comprendre ce qui est quoi.

Exemple 1

Imaginez que nous ayons un grand projet avec un tas d'anciens codes, où il y a une classe responsable de l'exportation des rapports à partir d'une base de données. La classe fonctionne de manière synchrone. Autrement dit, l'ensemble du système est inactif pendant que la base de données traite la demande. En moyenne, il faut 30 minutes pour générer un rapport. Par conséquent, le processus d'exportation commence à 00h30 et la direction reçoit le rapport le matin. Un audit a révélé qu'il serait préférable de pouvoir recevoir immédiatement le rapport pendant les heures normales de bureau. L'heure de début ne peut pas être reportée et le système ne peut pas bloquer pendant qu'il attend une réponse de la base de données. La solution consiste à modifier le fonctionnement du système, en générant et en exportant le rapport sur un thread séparé. Cette solution laissera le système fonctionner comme d'habitude et la direction recevra de nouveaux rapports. Cependant, il y a un problème : le code actuel ne peut pas être réécrit, car d'autres parties du système utilisent ses fonctionnalités. Dans ce cas, nous pouvons utiliser le modèle de proxy pour introduire une classe de proxy intermédiaire qui recevra les demandes d'exportation de rapports, enregistrera l'heure de début et lancera un thread séparé. Une fois le rapport généré, le fil se termine et tout le monde est content.

Exemple 2

Une équipe de développement est en train de créer un site Web d'événements. Pour obtenir des données sur les nouveaux événements, l'équipe interroge un service tiers. Une bibliothèque privée spéciale facilite l'interaction avec le service. Lors du développement, un problème est découvert : le système tiers met à jour ses données une fois par jour, mais une requête lui est envoyée à chaque fois qu'un utilisateur rafraîchit une page. Cela crée un grand nombre de requêtes et le service cesse de répondre. La solution consiste à mettre en cache la réponse du service et à renvoyer le résultat mis en cache aux visiteurs lorsque les pages sont rechargées, en mettant à jour le cache si nécessaire. Dans ce cas, le modèle de conception de proxy est une excellente solution qui ne modifie pas les fonctionnalités existantes.

Le principe du design pattern

Pour implémenter ce modèle, vous devez créer une classe proxy. Il implémente l'interface de la classe de service, imitant son comportement pour le code client. De cette manière, le client interagit avec un proxy au lieu de l'objet réel. En règle générale, toutes les demandes sont transmises à la classe de service, mais avec des actions supplémentaires avant ou après. En termes simples, un proxy est une couche entre le code client et l'objet cible. Prenons l'exemple de la mise en cache des résultats d'une requête à partir d'un disque dur ancien et très lent. Supposons que nous parlions d'un horaire pour les trains électriques dans une ancienne application dont la logique ne peut pas être modifiée. Un disque avec un horaire mis à jour est inséré chaque jour à une heure fixe. Donc nous avons:
  1. TrainTimetableinterface.
  2. ElectricTrainTimetable, qui implémente cette interface.
  3. Le code client interagit avec le système de fichiers via cette classe.
  4. TimetableDisplayclasse de clients. Sa printTimetable()méthode utilise les méthodes de la ElectricTrainTimetableclasse.
Le schéma est simple : Modèle de conception proxy : - 2Actuellement, à chaque appel de la printTimetable()méthode, la ElectricTrainTimetableclasse accède au disque, charge les données et les présente au client. Le système fonctionne bien, mais il est très lent. En conséquence, la décision a été prise d'augmenter les performances du système en ajoutant un mécanisme de mise en cache. Cela peut être fait en utilisant le modèle proxy : Modèle de conception proxy : - 3ainsi, la TimetableDisplayclasse ne remarque même pas qu'elle interagit avec la ElectricTrainTimetableProxyclasse au lieu de l'ancienne classe. La nouvelle implémentation charge l'horaire une fois par jour. Pour les requêtes répétées, il renvoie l'objet précédemment chargé de la mémoire.

Quelles sont les meilleures tâches pour un proxy ?

Voici quelques situations où ce modèle sera certainement utile :
  1. Mise en cache
  2. Initialisation retardée ou paresseuse Pourquoi charger un objet tout de suite si vous pouvez le charger au besoin ?
  3. Demandes de journalisation
  4. Vérification intermédiaire des données et accès
  5. Démarrage des threads de travail
  6. Enregistrer l'accès à un objet
Et il y a aussi d'autres cas d'utilisation. En comprenant le principe de ce modèle, vous pouvez identifier les situations où il peut être appliqué avec succès. À première vue, un proxy fait la même chose qu'une façade , mais ce n'est pas le cas. Un proxy a la même interface que l'objet de service. Aussi, ne confondez pas ce motif avec les motifs de décorateur ou d'adaptateur . Un décorateur fournit une interface étendue et un adaptateur fournit une interface alternative.

Avantages et inconvénients

  • + Vous pouvez contrôler l'accès à l'objet de service comme vous le souhaitez
  • + Capacités supplémentaires liées à la gestion du cycle de vie de l'objet de service
  • + Cela fonctionne sans objet de service
  • + Il améliore les performances et la sécurité du code.
  • - Il y a un risque que les performances se détériorent en raison de demandes supplémentaires
  • - Cela rend la hiérarchie des classes plus compliquée

Le modèle de procuration en pratique

Implémentons un système qui lit les horaires des trains depuis un disque dur :

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Voici la classe qui implémente l'interface principale :

public class ElectricTrainTimetable implements TrainTimetable {

   @Override
   public String[] getTimetable() {
       ArrayList<String> list = new ArrayList<>();
       try {
           Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               list.add(line);
           }
       } catch (IOException e) {
           System.err.println("Error:  " + e);
       }
       return list.toArray(new String[list.size()]);
   }

   @Override
   public String getTrainDepartureTime(String trainId) {
       String[] timetable = getTimetable();
       for (int i = 0; i < timetable.length; i++) {
           if (timetable[i].startsWith(trainId+";")) return timetable[i];
       }
       return "";
   }
}
Chaque fois que vous obtenez l'horaire des trains, le programme lit un fichier sur le disque. Mais ce n'est que le début de nos ennuis. Le fichier entier est lu à chaque fois que vous obtenez l'horaire d'un seul train ! C'est bien qu'un tel code n'existe que dans des exemples de ce qu'il ne faut pas faire :) Classe client :

public class TimetableDisplay {
   private TrainTimetable trainTimetable = new ElectricTrainTimetable();

   public void printTimetable() {
       String[] timetable = trainTimetable.getTimetable();
       String[] tmpArr;
       System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
       for (int i = 0; i < timetable.length; i++) {
           tmpArr = timetable[i].split(";");
           System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
       }
   }
}
Exemple de fichier :

9B-6854;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
Testons-le :

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Sortir:

Train  From  To  Departure time  Arrival time  Travel time
9B-6854  London  Prague  13:43  21:15  07:32
BA-1404  Paris  Graz  14:25  21:25  07:00
9B-8710  Prague  Vienna  04:48  08:49  04:01
9B-8122  Prague  Graz  04:48  08:49  04:01
Passons maintenant en revue les étapes requises pour introduire notre modèle :
  1. Définissez une interface qui permet l'utilisation d'un proxy au lieu de l'objet d'origine. Dans notre exemple, il s'agit de TrainTimetable.

  2. Créez la classe proxy. Il doit avoir une référence à l'objet de service (créez-le dans la classe ou passez-le au constructeur).

    Voici notre classe proxy :

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
      
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return trainTimetable.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return trainTimetable.getTrainDepartureTime(trainId);
       }
      
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    À ce stade, nous créons simplement une classe avec une référence à l'objet d'origine et lui transférons tous les appels.

  3. Implémentons la logique de la classe proxy. Fondamentalement, les appels sont toujours redirigés vers l'objet d'origine.

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           for (int i = 0; i < timetableCache.length; i++) {
               if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    Le getTimetable()vérifie si le tableau d'emploi du temps a été mis en cache en mémoire. Sinon, il envoie une requête pour charger les données à partir du disque et enregistre le résultat. Si l'horaire a déjà été demandé, il renvoie rapidement l'objet de la mémoire.

    Grâce à sa fonctionnalité simple, la méthode getTrainDepartureTime() n'a pas dû être redirigée vers l'objet d'origine. Nous avons simplement dupliqué sa fonctionnalité dans une nouvelle méthode.

    Ne fais pas ça. Si vous devez dupliquer le code ou faire quelque chose de similaire, quelque chose s'est mal passé et vous devez revoir le problème sous un angle différent. Dans notre exemple simple, nous n'avions pas d'autre option. Mais dans les projets réels, le code sera probablement écrit plus correctement.

  4. Dans le code client, créez un objet proxy au lieu de l'objet d'origine :

    
    public class TimetableDisplay {
       // Changed reference
       private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy();
    
       public void printTimetable() {
           String[] timetable = trainTimetable.getTimetable();
           String[] tmpArr;
           System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
           for (int i = 0; i < timetable.length; i++) {
               tmpArr = timetable[i].split(";");
               System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
           }
       }
    }
    

    Vérifier

    
    Train  From  To  Departure time  Arrival time  Travel time
    9B-6854  London  Prague  13:43  21:15  07:32
    BA-1404  Paris  Graz  14:25  21:25  07:00
    9B-8710  Prague  Vienna  04:48  08:49  04:01
    9B-8122  Prague  Graz  04:48  08:49  04:01
    

    Super, ça marche correctement.

    Vous pouvez également envisager l'option d'une usine qui crée à la fois un objet d'origine et un objet proxy, selon certaines conditions.

Avant de dire au revoir, voici un lien utile

C'est tout pour aujourd'hui! Ce ne serait pas une mauvaise idée de retourner aux leçons et d'essayer vos nouvelles connaissances dans la pratique :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION