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