pattern Livre
Transcription
pattern Livre
pattern Livre Page 115 Vendredi, 9. octobre 2009 10:31 10 11 PROXY Un objet ordinaire fait sa part de travail pour supporter l’interface publique qu’il annonce. Il peut néanmoins arriver qu’un objet légitime ne soit pas en mesure d’assumer cette responsabilité ordinaire. Cela peut se produire lorsque l’objet met beaucoup de temps à se charger, lorsqu’il s’exécute sur un autre ordinateur, ou lorsque vous devez intercepter des messages qui lui sont destinés. Dans de telles situations, un objet proxy peut prendre cette responsabilité vis-à-vis d’un client et transmettre les requêtes au moment voulu à l’objet cible sous-jacent. L’objectif du pattern PROXY est de contrôler l’accès à un objet en fournissant un intermédiaire pour cet objet. Un exemple classique : proxy d’image Un objet proxy possède généralement une interface qui est quasiment identique à celle de l’objet auquel il sert d’intermédiaire. Il accomplit sa tâche en transmettant lorsqu’il se doit les requêtes à l’objet sous-jacent auquel il contrôle l’accès. Un exemple classique du pattern PROXY intervient pour rendre plus transparent le chargement d’images volumineuses en mémoire. Imaginez que les images d’une application doivent apparaître dans des pages ou panneaux qui ne sont pas affichés initialement. Pour éviter de charger ces images avant qu’elles ne soient requises, vous pourriez leur substituer des proxies qui s’occuperaient de les charger à la demande. Cette section présente un exemple d’un tel proxy. Notez toutefois que les conceptions qui emploient le pattern PROXY sont parfois fragiles car elles s’appuient sur la transmission d’appels de méthodes à des objets sous-jacents. Cette transmission peut produire une conception fragile et coûteuse en maintenance. © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 116 Vendredi, 9. octobre 2009 10:31 10 116 Partie II Patterns de responsabilité Imaginez que vous soyez ingénieur chez Oozinoz et travailliez à un proxy d’image qui, pour des raisons de performances, affichera une petite image temporaire pendant le chargement d’une image plus volumineuse. Vous disposez d’un prototype opérationnel (voir Figure 11.1). Le code de cette application est contenu dans la classe ShowProxy du package app.proxy. Le code sous-jacent qui supporte cette application se trouve dans le package com.oozinoz.imaging. Figure 11.1 Les captures d’écran illustrent une mini-application avant, pendant et après le chargement d’une image volumineuse (cette image appartient au domaine public. Library of Congress, Prints and Photographs Division, Gottscho-Schleisner Collection [LC-G605-CT-00488]). L’interface utilisateur affiche l’une des trois images suivantes : une image indiquant que le chargement n’a pas encore commencé (Absent), une image indiquant que l’image est en cours de chargement (Loading…), ou l’image voulue. Lorsque l’application démarre, elle affiche Absent, une image JPEG créée à l’aide d’un outil de dessin. Lorsque l’utilisateur clique sur Load, une image Loading… prédéfinie s’affiche presque instantanément. Après quelques instants, l’image désirée apparaît. © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 117 Vendredi, 9. octobre 2009 10:31 10 Chapitre 11 PROXY 117 Un moyen aisé d’afficher une image enregistrée au format JPEG, par exemple, est d’utiliser un objet ImageIcon comme argument d’un "label" qui affichera l’image : ImageIcon icon = new ImageIcon("images/fest.jpg"); JLabel label = new JLabel(icon); Dans l’application que vous développez, vous voulez passer à JLabel un proxy qui transmettra les requêtes de dessin de l’écran (paint) à : (1) une image Absent, (2) une image Loading…, ou (3) l’image désirée. Le flux des messages est représenté dans le diagramme de séquence de la Figure 11.2. Figure 11.2 Un objet ImageIconProxy transmet les requêtes paint() à l’objet ImageIcon :ImageIconProxy :Client :JLabel courant. current:ImageIcon paint() paint() paint() Lorsque l’utilisateur clique sur Load, votre code fait en sorte que l’image courante de l’objet ImageIconProxy devienne Loading…, et le proxy entame le chargement de l’image attendue. Une fois celle-ci complètement chargée, elle devient l’image courante de ImageIconProxy. Pour créer un proxy, vous pouvez dériver une sous-classe de ImageIcon, comme le montre la Figure 11.3. Le code de ImageIconProxy définit deux variables statiques contenant les images Absent et Loading… : static final ImageIcon ABSENT = new ImageIcon( ClassLoader.getSystemResource("images/absent.jpg")); static final ImageIcon LOADING = new ImageIcon( ClassLoader.getSystemResource("images/loading.jpg")); © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 118 Vendredi, 9. octobre 2009 10:31 10 118 Partie II Patterns de responsabilité Exécutable ImageIcon //~25 champs non inclus ici getIconHeight():int getIconWidth():int ImageIconProxy ABSENT:ImageIcon LOADING:ImageIcon current:ImageIcon paintIcon( c:Component, g:Graphics, x:int, y:int) ImageIconProxy( filename:String) //~50 autres méthodes getIconHeight():int load(callback:JFrame) run() getIconWidth():int paintIcon() Figure 11.3 Un objet ImageIconProxy peut remplacer un objet ImageIcon puisqu’il s’agit en fait d’un objet ImageIcon. Le constructeur de ImageIconProxy reçoit le nom d’un fichier d’image à charger. Lorsque la méthode load() d’un objet ImageIconProxy est invoquée, elle définit l’image comme étant LOADING et lance un thread séparé pour charger l’image. Le fait d’employer un thread séparé évite à l’application de devoir patienter pendant le chargement. La méthode load() reçoit un objet JFrame qui est rappelé par la méthode run() à l’issue du chargement. Voici le code presque complet de ImageIconProxy.java : package com.oozinoz.imaging; import java.awt.*; import javax.swing.*; public class ImageIconProxy extends ImageIcon implements Runnable { static final ImageIcon ABSENT = new ImageIcon( ClassLoader.getSystemResource("images/absent.jpg")); static final ImageIcon LOADING = new ImageIcon( ClassLoader.getSystemResource("images/loading.jpg")); ImageIcon current = ABSENT; protected String filename; protected JFrame callbackFrame; public ImageIconProxy(String filename) { © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 119 Vendredi, 9. octobre 2009 10:31 10 Chapitre 11 PROXY 119 super(ABSENT.getImage()); this.filename = filename; } public void load(JFrame callbackFrame) { this.callbackFrame = callbackFrame; current = LOADING; callbackFrame.repaint(); new Thread(this).start(); } public void run() { current = new ImageIcon( ClassLoader.getSystemResource(filename)); callbackFrame.pack(); } public int getIconHeight() { /* Exercice ! */ } public int getIconWidth() { /* Exercice ! */ } public synchronized void paintIcon( Component c, Graphics g, int x, int y) { // Exercice ! } } Exercice 11.1 Un objet ImageIconProxy accepte trois appels d’affichage d’image qu’il doit passer à l’image courante. Ecrivez le code des méthodes getIconHeight(), getIconWidth() et paintIcon() de la classe ImageIconProxy. b Les solutions des exercices de ce chapitre sont données dans l’Annexe B. Imaginez que vous parveniez à faire fonctionner le code de cette petite application de démonstration. Avant de créer la véritable application, laquelle ne se limite pas à un bouton Load, vous procédez à une révision de la conception, qui révèle toute sa fragilité. Exercice 11.2 La classe ImageIconProxy ne constitue pas un composant réutilisable bien conçu. Citez deux problèmes de cette conception. © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 120 Vendredi, 9. octobre 2009 10:31 10 120 Partie II Patterns de responsabilité Lorsque vous révisez la conception d’un développeur, vous devez à la fois comprendre cette conception et vous former une opinion à son sujet. Il se peut que le développeur pense avoir utilisé un pattern spécifique alors que vous doutez qu’il soit présent. Dans l’exemple précédent, le pattern PROXY apparaît de manière évidente mais ne garantit en rien que la conception soit bonne. Il existe d’ailleurs de bien meilleures conceptions. Lorsque ce pattern est présent, il doit pouvoir être justifié car la transmission de requêtes peut entraîner des problèmes que d’autres conceptions permettraient d’éviter. La prochaine section devrait vous aider à déterminer si le pattern PROXY est une option valable pour votre conception. Reconsidération des proxies d’image A ce stade, peut-être vous demandez-vous si les patterns de conception vous ont été d’une quelconque aide. Vous avez implémenté fidèlement un pattern et voilà que vous cherchez maintenant à vous en débarrasser. Il s’agit en fait d’une étape naturelle et même saine, qui survient plus souvent dans des conditions réelles de développement que dans les livres. En effet, un auteur peut, avec l’aide de ses relecteurs, repenser et remplacer une conception de qualité insuffisante avant que son ouvrage ne soit publié. Dans la pratique, un pattern peut vous aider à faire fonctionner une application et faciliter les discussions sur sa conception. Dans l’exemple de ImageIconProxy, le pattern a servi à cela, même s’il est beaucoup plus simple d’obtenir l’effet désiré sans implémenter littéralement un proxy. La classe ImageIcon opère sur un objet Image. Plutôt que de transmettre les requêtes de dessin de l’écran à un objet ImageIcon séparé, il est plus facile d’opérer sur l’objet Image enveloppé dans ImageIcon. La Figure 11.4 présente une classe LoadingImageIcon (tirée du package com.oozinoz.imaging) qui possède seulement deux méthodes, load() et run(), en plus de ses constructeurs. Figure 11.4 La classe LoadingImageIcon fonctionne en changeant l’objet Image qu’elle contient. ImageIcon image:Image LoadingImageIcon ABSENT:ImageIcon LOADING:ImageIcon getImage():Image setImage(i:Image) LoadingImageIcon( filename:String) load(callback:JFrame) run() © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 121 Vendredi, 9. octobre 2009 10:31 10 Chapitre 11 PROXY 121 La méthode load() de cette classe révisée reçoit toujours un objet JFrame à rappeler après le chargement de l’image souhaitée. Lorsqu’elle s’exécute, elle invoque setImage() avec l’image de LOADING, redessine le cadre (frame) et lance un thread séparé pour elle-même. La méthode run(), qui s’exécute dans un thread séparé, crée un nouvel objet ImageIcon pour le fichier nommé dans le constructeur, appelle setImage() avec l’image de cet objet et redessine le cadre. Voici le code presque complet de LoadingImageIcon.java : package com.oozinoz.imaging; import javax.swing.ImageIcon; import javax.swing.JFrame; public class LoadingImageIcon extends ImageIcon implements Runnable { static final ImageIcon ABSENT = new ImageIcon( ClassLoader.getSystemResource("images/absent.jpg")); static final ImageIcon LOADING = new ImageIcon( ClassLoader.getSystemResource("images/loading.jpg")); protected String filename; protected JFrame callbackFrame; public LoadingImageIcon(String filename) { super(ABSENT.getImage()); this.filename = filename; } public void load(JFrame callbackFrame) { // Exercice ! } public void run() { // Exercice ! } } Exercice 11.3 Ecrivez le code des méthodes load() et run() de LoadingImageIcon. Ce code révisé est moins lié à la conception de ImageIcon, s’appuyant principalement sur getImage() et setImage() et non sur la transmission d’appels. En fait, il n’y a pas du tout de transmission. LoadingImageIcon a seulement l’apparence d’un proxy, et non la structure. © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 122 Vendredi, 9. octobre 2009 10:31 10 122 Partie II Patterns de responsabilité Le fait que le pattern PROXY ait recours à la transmission peut accroître la maintenance du code. Par exemple, si l’objet sous-jacent change, l’équipe d’Oozinoz devra actualiser le proxy. Pour éviter cela, vous devriez lorsque vous le pouvez renoncer à ce pattern. Il existe cependant des situations où vous n’avez d’autre choix que de l’utiliser. En particulier, lorsque l’objet pour lequel vous devez intercepter des messages s’exécute sur une autre machine, ce pattern est parfois la seule option envisageable. Proxy distant Lorsque vous voulez invoquer une méthode d’un objet qui s’exécute sur un autre ordinateur, vous ne pouvez le faire directement et devez donc trouver un autre moyen de communiquer avec lui. Vous pourriez ouvrir un socket sur l’hôte distant et élaborer un protocole pour envoyer des messages à l’objet. Idéalement, une telle approche vous permettrait de lui passer des messages de la même manière que s’il était local. Vous devriez pouvoir appeler les méthodes d’un objet proxy qui transmettrait ces requêtes à l’objet distant. En fait, de telles conceptions ont déjà été implémentées, notamment dans CORBA (Common Object Request Broker Architecture), dans ASP.NET (Active Server Pages for .NET), et dans Java RMI (Remote Method Invocation). Grâce à RMI, un client peut assez aisément obtenir un objet proxy qui transmette les appels vers l’objet désiré actif sur une autre machine. Il importe de connaître RMI puisque ce mécanisme fait partie des fondements de la spécification EJB (Enterprise JavaBeans), un standard important de l’industrie. Indépendamment de la façon dont les standards de l’industrie évoluent, le pattern PROXY continuera de jouer un rôle important dans les environnements distribués, du moins dans un avenir proche, et RMI représente un bon exemple d’implémentation de ce pattern. Pour vous familiariser avec RMI, vous aurez besoin d’un ouvrage de référence sur le sujet, tel que JavaTM Enterprise in a Nutshel (Java en concentré : Manuel de référence pour Java) [Flanagan et al. 2002]. L’exemple présenté dans cette section n’est pas un tutoriel sur RMI mais permet de mettre en évidence la présence et l’importance du pattern PROXY dans les applications RMI. Nous laisserons de côté les difficultés de conception introduites par RMI et EJB. Supposez que vous ayez décidé d’explorer le fonctionnement de RMI en rendant les méthodes d’un objet accessibles à un programme Java qui s’exécute sur un autre © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake pattern Livre Page 123 Vendredi, 9. octobre 2009 10:31 10 Chapitre 11 PROXY 123 ordinateur. La première étape de développement consiste à créer une interface pour la classe qui doit être accessible à distance. Vous commencez par créer une interface Rocket qui est indépendante du code existant à Oozinoz : package com.oozinoz.remote; import java.rmi.*; public interface Rocket extends Remote { void boost(double factor) throws RemoteException; double getApogee() throws RemoteException; double getPrice() throws RemoteException; } L’interface Rocket étend Remote et ses méthodes déclarent toutes qu’elles génèrent (throw) des exceptions distantes (RemoteException). Expliquer cet aspect de l’interface dépasse le cadre du présent livre, mais n’importe quel ouvrage didacticiel sur RMI devrait le faire. Votre référence RMI devrait également expliquer que, pour agir en tant que serveur, l’implémentation de votre interface distante peut étendre UnicastRemoteObject, comme illustré Figure 11.5. Figure 11.5 Pour utiliser RMI, vous pouvez d’abord définir l’interface souhaitée pour les messages échangés entre les deux ordinateurs puis créer une sous-classe de UnicastRemoteObject qui implémente cette interface. «interface» Rocket boost(factor:double) getApogee():double getPrice():double UnicastRemoteObject RocketImpl apogee:double price:double RocketImpl(price:double,apogee:double) boost(factor:double) getApogee():double getPrice():double © 2009 Pearson Education France – Les design patterns en Java – Steven John Metsker & William C. Wake