Design Pattern – TD n°6 : LE PATTERN PROXY : CHANGER UN COMPORTEMENT SANS CHANGER L'OBJET
Vous êtes de retour au club Jamba. Après le bon travail réalisé sur René la Taupe, vous êtes placé sur un projet prioritaire : le Jamba Album Cover Generator. Ce petit programme permet de télécharger, sur son mobile, les pochettes des CDs les plus populaires du marché.
Problème initial
Les classes Main, FenetrePrincipale et RecupererAlbum sont déjà utilisées par le client et ne peuvent être modifiées sans une mise à jour du programme. La classe AfficheurAlbum, quant à elle, est hébergée sur un serveur extérieur loué par le club Jamba, et toute modification nécessite un paiement.
Objectif
L'idée est d'afficher une image d'attente aux clients pendant le téléchargement de la pochette de l'album sélectionné. Vous ne pouvez modifier que les classes existantes ou ajouter de nouvelles classes, en particulier FactoryRecuperationAlbum.
Indices pour la résolution
1. Créez une classe Proxy qui implémente RecuperationAlbum. Elle devra se substituer à cette classe aux yeux du client et modifier la méthode afficheralbum() pour inclure l'affichage d'une image d'attente.
2. Le proxy devra utiliser certaines méthodes de AfficheurAlbum pour récupérer l'image, mais pas pour l'afficher.
3. Dans MyImage, utilisez la méthode replacewith() pour remplacer une image déjà affichée par une autre.
4. Modifiez FactoryRecuperationAlbum pour intégrer le proxy.
Tests et corrections
1. Testez l'application en vérifiant que le bouton de sélection ne reste pas bloqué en position cliquée après un téléchargement.
2. Utilisez des threads pour éviter ce blocage, comme dans la classe AfficheurAlbum.
3. Modifiez le proxy pour bloquer la création en cascade de couvertures d'albums si le client appuie plusieurs fois sur le bouton.
Question subsidiaire
Grâce au pattern Proxy, on évite la modification des classes « côté client » sans altérer leur structure ou leur interface.
Le Pattern Chaîne de Responsabilité : Gestion de Processeurs
Le pattern chaîne de responsabilité permet d'exécuter des fonctions de manière séquentielle, comme le Décorateur, mais avec une différence majeure : il délègue les responsabilités. Si un processeur ne peut pas traiter une requête, il la transmet au suivant dans la chaîne.
Objectif
Complétez la classe MathCompute pour calculer la décomposition en facteurs premiers du nombre stocké dans la variable num. Ajoutez chaque facteur dans la liste res.
Processeurs et gestion des tâches
Créez trois processeurs rapides et deux processeurs lents. Chaque processeur doit gérer les calculs en utilisant la méthode runTask, qui calcule la décomposition en facteurs premiers d'un nombre. Si un processeur est déjà occupé, il attendra la fin du calcul avant d'en commencer un nouveau.
Utilisez Thread.sleep pour simuler la durée du traitement. Un objet de type ResultPool doit être créé dans le main et partagé entre les processeurs pour collecter les résultats.
Classes à implémenter
1. QuickProcessorHandler : Gère les processeurs rapides et contient un processeur p de ce type. Implémente la méthode handlerequest(int n) qui calcule la décomposition ou délègue à nextprocessor si le processeur est occupé.
2. SlowProcessorHandler : Fonctionne de manière similaire pour les processeurs lents.
3. ProcessorHandler (abstraite) : Classe parente de QuickProcessorHandler et SlowProcessorHandler. Contient la variable nextprocessor et son setter, ainsi qu'une méthode handlerequest non abstraite pour gérer le cas où le processeur est occupé.
Gestion des requêtes
Le main doit créer plusieurs ProcessorHandler, les lier entre eux et lancer les requêtes de calcul. Si tous les processeurs sont occupés, affichez un message et abandonnez la requête.
Pour chaque nombre, affichez un message indiquant quel processeur le traite.
Optimisation de la chaîne
Si tous les processeurs sont occupés, bouclez sur le premier processeur de la file pour tenter de traiter la requête. Ajoutez un léger Thread.sleep entre les délégations pour éviter une boucle trop rapide.
Mécanisme similaire en Java
Un mécanisme connu en Java qui ressemble à la Chaîne de Responsabilité est le Event Handling, où les événements sont propagés à travers une chaîne d'écouteurs.
Le Pattern Bridge : Éviter une Explosion du Nombre de Classes
Le pattern Bridge permet de séparer les abstractions des implémentations pour éviter la prolifération de classes. Par exemple, dans le TP n°2 avec l'Abstract Factory, chaque ajout de fonctionnalité ou d'OS nécessitait la création de plusieurs classes.
Objectif
Créez une interface MaListe qui gère des ensembles d'entiers avec les méthodes push, pop et isEmpty. Cette interface sera déclinée par les classes abstraites Lifo, Fifo, Random et Middle.
Implémentations possibles
Chaque type de liste (Lifo, Fifo, etc.) peut être implémenté soit avec un tableau, soit avec une liste chaînée. Pour cela, créez une interface MonImplementation avec des méthodes comme getsize, removeElementAt et addElementAt, puis implémentez-la avec les classes ArrayList et LinkedList.
Structure du projet
La classe abstraite MaListe contiendra un objet de type MonImplementation et déclarera les méthodes pop, push et isEmpty comme abstraites. Les classes Fifo, Lifo, Random et Middle hériteront de MaListe pour implémenter les comportements spécifiques.
Avantage du Bridge
Cette séparation évite la création de nombreuses classes. Par exemple, si vous ajoutez une nouvelle implémentation (comme un arbre binaire), vous n'aurez besoin que d'une seule classe pour la nouvelle structure, et non d'une classe par type de liste.
Observateur et Observé
L'objectif est de créer une horloge qui se met à jour toutes les secondes et qui notifie ses observateurs (fenêtres) de ces changements.
Interfaces et implémentations
1. Observable : Interface que l'horloge doit implémenter, avec les méthodes addObservateur, updateObservateur et delObservateur.
2. Observateur : Interface que les fenêtres doivent implémenter, avec la méthode update(String hour).
Fonctionnement
L'horloge (observé) doit connaître ses observateurs et les notifier à chaque changement d'heure via la méthode updateObservateur.
Exercice 2 : Adaptation avec le Builder
Reprenez le code de l'exercice 2 du contrôle pour adapter la fonction load3denvironments et la classe GraphicMultiPlatform.
Modifications requises
1. Pour Iphone, la fonction load3denvironments doit maintenant faire : operationsdone[5] = true.
2. Pour PC, la fonction load3denvironments doit maintenant faire : operationsdone[0] = false.
Nouvelle méthode display
Adaptez votre code avec la nouvelle méthode display de GraphicMultiPlatform qui vérifie les états des opérations pour afficher les graphiques correctement.
Conditions d'affichage
La méthode display affiche les graphiques selon les combinaisons suivantes :
- Si !operationsdone[0] && !operationsdone[1] && !operationsdone[2] && operationsdone[3] && !operationsdone[4] && !operationsdone[5] && operationsdone[6] && !operationsdone[7] && !operationsdone[8] && operationsdone[9] && operationsdone[10] && operationsdone[11], affichez "Load PC graphics ok".
- Si !operationsdone[0] && operationsdone[1] && !operationsdone[2] && !operationsdone[3] && operationsdone[4] && operationsdone[5] && operationsdone[6] && !operationsdone[7] && operationsdone[8] && !operationsdone[9] && operationsdone[10] && operationsdone[11], affichez "Load IPhone graphics ok".
- Si !operationsdone[0] && !operationsdone[1] && operationsdone[2] && !operationsdone[3] && !operationsdone[4] && operationsdone[5] && !operationsdone[6] && operationsdone[7] && operationsdone[8] && !operationsdone[9] && !operationsdone[10] && operationsdone[11], affichez "Load Gameboy graphics ok".
- Sinon, affichez une image d'erreur et "Error loading graphics".
FAQ
1. Pourquoi utiliser le pattern Proxy ?
Le pattern Proxy permet de modifier le comportement d'un objet sans altérer sa structure ou son interface. Il est idéal pour ajouter des fonctionnalités comme le chargement d'une image d'attente tout en préservant l'intégrité des classes existantes.
2. Comment gérer les processeurs occupés avec la Chaîne de Responsabilité ?
En utilisant une boucle qui délègue la requête au processeur suivant dans la chaîne, et en ajoutant un léger Thread.sleep pour éviter une surcharge du système.
3. Quel est l'intérêt du pattern Bridge ?
Le pattern Bridge réduit la complexité de conception en séparant les abstractions des implémentations, évitant ainsi la création d'un nombre excessif de classes lors de l'ajout de nouvelles fonctionnalités ou structures.