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:TrainTimetable
interface.ElectricTrainTimetable
, qui implémente cette interface.- Le code client interagit avec le système de fichiers via cette classe.
TimetableDisplay
classe de clients. SaprintTimetable()
méthode utilise les méthodes de laElectricTrainTimetable
classe.

printTimetable()
méthode, la ElectricTrainTimetable
classe 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 : 
TimetableDisplay
classe ne remarque même pas qu'elle interagit avec la ElectricTrainTimetableProxy
classe 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 :- Mise en cache
- Initialisation retardée ou paresseuse Pourquoi charger un objet tout de suite si vous pouvez le charger au besoin ?
- Demandes de journalisation
- Vérification intermédiaire des données et accès
- Démarrage des threads de travail
- Enregistrer l'accès à un objet
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 :
-
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
. -
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.
-
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.
-
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.
GO TO FULL VERSION