JavaRush /Blog Java /Random-FR /Modèle de conception de proxy

Modèle de conception de proxy

Publié dans le groupe Random-FR
En programmation, il est important de bien planifier l’architecture des applications. Les modèles de conception sont un outil indispensable pour cela. Aujourd'hui, nous parlerons de Proxy, ou en d'autres termes, de Député.

Pourquoi avez-vous besoin d'un adjoint ?

Ce modèle permet de résoudre les problèmes associés à l'accès contrôlé à un objet. Vous vous posez peut-être la question : « Pourquoi avons-nous besoin d’un tel accès contrôlé ? » Examinons quelques situations qui vous aideront à comprendre de quoi il s'agit.

Exemple 1

Imaginons que nous ayons un grand projet avec un tas de code ancien, dans lequel se trouve une classe chargée de télécharger les rapports à partir de la base de données. La classe fonctionne de manière synchrone, c'est-à-dire que l'ensemble du système est inactif pendant que la base de données traite la requête. En moyenne, un rapport est généré en 30 minutes. Grâce à cette fonctionnalité, son téléchargement commence à 00h30 et la direction reçoit ce rapport dans la matinée. Au cours de l'analyse, il s'est avéré qu'il est nécessaire de recevoir le rapport immédiatement après sa génération, c'est-à-dire dans la journée. Il est impossible de reprogrammer l'heure de début, car le système attendra une réponse de la base de données. La solution est de changer le principe de fonctionnement en démarrant le téléchargement et la génération du rapport dans un thread séparé. Cette solution permettra au système de 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, puisque ses fonctions sont utilisées par d'autres parties du système. Dans ce cas, vous pouvez introduire une classe proxy intermédiaire en utilisant le modèle adjoint, qui recevra une demande de téléchargement d'un rapport, enregistrera l'heure de début et lancera un fil de discussion séparé. Lorsque le rapport sera généré, le fil terminera son travail et tout le monde sera content.

Exemple 2

L'équipe de développement crée un site Web d'affiches. Pour obtenir des données sur les nouveaux événements, ils se tournent vers un service tiers, avec lequel l'interaction est mise en œuvre via une bibliothèque fermée spéciale. Au cours du développement, un problème est survenu : un système tiers met à jour les données une fois par jour et une demande lui est adressée à chaque fois que l'utilisateur actualise la page. Cela crée un grand nombre de requêtes et le service ne répond plus. La solution consiste à mettre en cache la réponse du service et à fournir aux visiteurs le résultat enregistré à chaque redémarrage, en mettant à jour ce cache si nécessaire. Dans ce cas, utiliser le modèle adjoint est une excellente solution sans modifier la fonctionnalité finie.

Comment fonctionne le modèle

Pour implémenter ce modèle, vous devez créer une classe proxy. Il implémente une interface de classe de service, simulant son comportement pour le code client. Ainsi, au lieu de l'objet réel, le client interagit avec son proxy. Généralement, toutes les requêtes sont transmises à la classe de service, mais avec des actions supplémentaires avant ou après son appel. En termes simples, cet objet proxy est une couche entre le code client et l'objet cible. Regardons un exemple de mise en cache d'une requête à partir d'un ancien disque très lent. Qu'il s'agisse d'un horaire de train électrique dans une application ancienne, dont le principe de fonctionnement ne peut être modifié. Le disque avec le planning mis à jour est inséré chaque jour à heure fixe. Donc nous avons:
  1. Interfaces TimetableTrains.
  2. La classe TimetableElectricTrainsqui implémente cette interface.
  3. C'est via cette classe que le code client interagit avec le système de fichiers disque.
  4. Classe cliente DisplayTimetable. Sa méthode printTimetable()utilise des méthodes de classe TimetableElectricTrains.
Le schéma est simple : Modèle de conception de proxy - 2actuellement, chaque fois qu'une méthode est appelée, printTimetable()la classe TimetableElectricTrainsaccède au disque, décharge les données et les fournit au client. Ce système fonctionne bien, mais il est très lent. Par conséquent, il a été décidé 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 de proxy - 3de cette façon, la classe DisplayTimetablene remarquera même pas qu'elle interagit avec la classe TimetableElectricTrainsProxyet non avec la précédente. La nouvelle implémentation charge le planning une fois par jour et, lors de demandes répétées, renvoie l'objet déjà chargé depuis la mémoire.

Pour quelles tâches est-il préférable d’utiliser Proxy ?

Voici quelques situations dans lesquelles ce modèle sera certainement utile :
  1. Mise en cache.
  2. L’implémentation paresseuse est également connue sous le nom d’implémentation paresseuse. Pourquoi charger un objet en une seule fois alors que vous pouvez le charger selon vos besoins ?
  3. Demandes de journalisation.
  4. Contrôles provisoires des données et des accès.
  5. Lancement de threads de traitement parallèle.
  6. Enregistrer ou compter l'historique d'un appel.
Il existe également d'autres cas d'utilisation. Comprenant le principe de fonctionnement de ce modèle, vous pouvez vous-même en trouver une application réussie. À première vue, Representative fait la même chose que Facade , mais ce n'est pas le cas. Le proxy a la même interface que l'objet de service. Ne confondez pas non plus le motif avec Decorator ou Adapter . Le décorateur fournit une interface étendue, tandis que l'adaptateur en propose une alternative.

Avantages et inconvénients

  • + Vous pouvez contrôler l'accès à l'objet de service à votre guise ;
  • + Capacités supplémentaires pour gérer le cycle de vie d'un objet de service ;
  • + Fonctionne sans objet de service ;
  • + Améliore les performances et la sécurité du code.
  • - Il existe un risque de dégradation des performances due à des traitements supplémentaires ;
  • - Complique la structure des classes de programme.

Modèle de substitution en pratique

Implémentons avec vous un système qui lit les horaires des trains à partir du disque :
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Une classe qui implémente l'interface principale :
public class TimetableElectricTrains implements TimetableTrains {

   @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 essayez d'obtenir l'horaire de tous les trains, le programme lit le fichier sur le disque. Mais ce sont quand même des fleurs. Le fichier est également lu à chaque fois que vous avez besoin d'obtenir l'horaire d'un seul train ! C'est bien qu'un tel code n'existe que dans de mauvais exemples :) Classe client :
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       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;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Testons :
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Conclusion:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Passons maintenant aux étapes de mise en œuvre de notre modèle :
  1. Définissez une interface qui vous permet d'utiliser un nouveau proxy à la place de l'objet d'origine. Dans notre exemple, c'est TimetableTrains.

  2. Créez une classe proxy. Il doit contenir une référence à un objet de service (créé dans une classe ou transmis dans un constructeur) ;

    Voici notre classe proxy :

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

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

  3. Nous implémentons la logique de la classe proxy. Fondamentalement, l'appel est toujours redirigé vers l'objet d'origine.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    La méthode getTimetable()vérifie si le tableau de planification est mis en cache en mémoire. Sinon, il émet une demande de chargement des données à partir du disque, stockant ainsi le résultat. Si la requête est déjà en cours d'exécution, elle renverra rapidement un objet de la mémoire.

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

    Vous ne pouvez pas faire ça. Si vous avez dû dupliquer du code ou effectuer des manipulations similaires, cela signifie que quelque chose s'est mal passé et que vous devez examiner le problème sous un angle différent. Dans notre exemple simple, il n'y a pas d'autre moyen, mais dans des projets réels, le code sera très probablement écrit plus correctement.

  4. Remplacez la création de l'objet d'origine dans le code client par un objet de remplacement :

    public class DisplayTimetable {
       // Измененная link
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           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]);
           }
       }
    }

    Examen

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Super, ça fonctionne correctement.

    Vous pouvez également envisager une usine qui créera à la fois l’objet d’origine et un objet de remplacement en fonction de certaines conditions.

Lien utile au lieu d'un point

  1. Excellent article sur les modèles et un peu sur « Adjoint »

C'est tout pour aujourd'hui! Ce serait bien de recommencer à apprendre et de tester vos nouvelles connaissances dans la pratique :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION