Prenez le contrôle avec le modèle de conception Proxy

Un de mes amis - un médecin, pas moins - m'a dit une fois qu'il avait convaincu un ami de passer un examen universitaire pour lui. Quelqu'un qui prend la place de quelqu'un d'autre est connu comme un mandataire. Malheureusement pour mon ami, son mandataire a bu un peu trop la veille et a échoué au test.

Dans les logiciels, le modèle de conception Proxy s'avère utile dans de nombreux contextes. Par exemple, à l'aide du Java XML Pack, vous utilisez des proxys pour accéder aux services Web avec JAX-RPC (API Java pour les appels de procédure distante basés sur XML). L'exemple 1 montre comment un client accède à un service Web Hello World simple:

Exemple 1. Un proxy SOAP (Simple Object Access Protocol)

classe publique HelloClient {public static void main (String [] args) {try {HelloIF_Stub proxy = (HelloIF_Stub) (new HelloWorldImpl (). getHelloIF ()); proxy ._setTargetEndpoint (args [0]); System.out.println ( proxy .sayHello ("Duke!")); } catch (Exception ex) {ex.printStackTrace (); }}}

Le code de l'exemple 1 ressemble étroitement à l'exemple des services Web Hello World inclus avec JAX-RPC. Le client obtient une référence au proxy et définit le point de terminaison du proxy (l'URL du service Web) avec un argument de ligne de commande. Une fois que le client a une référence au proxy, il appelle la sayHello()méthode du proxy . Le proxy transmet cet appel de méthode au service Web, qui réside souvent sur une machine différente de celle du client.

L'exemple 1 illustre une utilisation du modèle de conception Proxy: l'accès aux objets distants. Les proxies s'avèrent également utiles pour créer des ressources coûteuses à la demande, un proxy virtuel, et pour contrôler l'accès aux objets, un proxy de protection.

Si vous avez lu mon "Décorez votre code Java" ( JavaWorld, décembre 2001), vous pouvez voir des similitudes entre les modèles de conception Decorator et Proxy. Les deux modèles utilisent un proxy qui transmet les appels de méthode à un autre objet, connu sous le nom de sujet réel. La différence est qu'avec le modèle Proxy, la relation entre un proxy et le sujet réel est généralement définie au moment de la compilation, tandis que les décorateurs peuvent être construits de manière récursive au moment de l'exécution. Mais je devance moi-même.

Dans cet article, je présente d'abord le modèle Proxy, en commençant par un exemple de proxy pour les icônes Swing. Je conclus par un aperçu du support intégré du JDK pour le modèle Proxy.

Remarque: Dans les deux premiers chapitres de cette colonne - «Surprenez vos amis développeurs avec des modèles de conception» (octobre 2001) et «Décorez votre code Java» - j'ai discuté du modèle Decorator, qui est étroitement lié au modèle Proxy, vous souhaitera peut-être consulter ces articles avant de continuer.

Le modèle Proxy

Proxy: contrôlez l'accès à un objet avec un proxy (également appelé substitut ou espace réservé).

Les icônes Swing, pour les raisons évoquées dans la section «Applicabilité du proxy» ci-dessous, représentent un excellent choix pour illustrer le modèle de proxy. Je commence par une brève introduction aux icônes Swing, suivie d'une discussion sur un proxy d'icône Swing.

Icônes de balançoire

Les icônes Swing sont de petites images utilisées dans les boutons, les menus et les barres d'outils. Vous pouvez également utiliser les icônes Swing seules, comme l'illustre la figure 1.

L'application illustrée à la figure 1 est répertoriée dans l'exemple 2:

Exemple 2. Icônes Swing

import java.awt. *; import java.awt.event. *; import javax.swing. *; // Cette classe teste une icône d'image. La classe publique IconTest étend JFrame {chaîne statique privée IMAGE_NAME = "mandrill.jpg"; int statique privé FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 268, FRAME_HEIGHT = 286; Icône privée imageIcon = null, imageIconProxy = null; static public void main (String args []) {IconTest app = new IconTest (); app.show (); } public IconTest () {super ("Icon Test"); imageIcon = nouvel ImageIcon (IMAGE_NAME); setBounds (FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); } peinture publique vide (Graphiques g) {super.paint (g); Insets insets = getInsets (); imageIcon.paintIcon (this, g, insets.left, insets.top); }}

L'application précédente crée une icône d'image - une instance de javax.swing.ImageIcon- puis remplace la paint()méthode pour peindre l'icône.

Swing des proxys d'icônes d'image

L'application illustrée à la figure 1 est une mauvaise utilisation des icônes d'image Swing car vous ne devez utiliser les icônes d'image que pour les petites images. Cette restriction existe car la création d'images est coûteuse et les ImageIconinstances créent leurs images lorsqu'elles sont construites. Si une application crée plusieurs grandes images à la fois, cela peut entraîner une baisse significative des performances. De plus, si l'application n'utilise pas toutes ses images, il est inutile de les créer à l'avance.

Une meilleure solution charge les images au fur et à mesure qu'elles deviennent nécessaires. Pour ce faire, un proxy peut créer l'icône réelle la première fois que la paintIcon()méthode du proxy est appelée. La figure 2 montre une application qui contient une icône d'image (à gauche) et un proxy d'icône d'image (à droite). L'image du haut montre l'application juste après son lancement. Étant donné que les icônes d'image chargent leurs images lorsqu'elles sont construites, l'image d'une icône s'affiche dès que la fenêtre de l'application s'ouvre. En revanche, le proxy ne charge pas son image tant qu'elle n'est pas peinte pour la première fois. Jusqu'à ce que l'image se charge, le proxy dessine une bordure autour de son périmètre et affiche "Chargement de l'image ..." L'image du bas de la figure 2 montre l'application après que le proxy a chargé son image.

J'ai répertorié l'application illustrée à la figure 2 dans l'exemple 3:

Exemple 3. Proxy d'icône Swing

import java.awt. *; import java.awt.event. *; import javax.swing. *; // Cette classe teste un proxy virtuel, qui est un proxy qui // retarde le chargement d'une ressource coûteuse (une icône) jusqu'à ce que // cette ressource soit nécessaire. classe publique VirtualProxyTest étend JFrame {chaîne statique privée IMAGE_NAME = "mandrill.jpg"; int statique privé IMAGE_WIDTH = 256, IMAGE_HEIGHT = 256, SPACING = 5, FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 530, FRAME_HEIGHT = 286; Icône privée imageIcon = null, imageIconProxy = null; static public void main (String args []) {VirtualProxyTest app = new VirtualProxyTest (); app.show (); } public VirtualProxyTest () {super ("Test du proxy virtuel"); // Crée une icône d'image et un proxy d'icône d'image. imageIcon = nouvel ImageIcon (IMAGE_NAME); imageIconProxy = nouveauImageIconProxy (IMAGE_NAME, IMAGE_WIDTH, IMAGE_HEIGHT); // Définit les limites du cadre et l'opération de fermeture // par défaut du cadre. setBounds (FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); } peinture publique vide (Graphiques g) {super.paint (g); Insets insets = getInsets (); imageIcon.paintIcon (this, g, insets.left, insets.top); imageIconProxy.paintIcon (this, g, insets.left + IMAGE_WIDTH + SPACING, // largeur insets.top); // la taille } }

L'exemple 3 est presque identique à l'exemple 2, sauf pour l'ajout du proxy image-icône. L'application Exemple 3 crée l'icône et le proxy dans son constructeur et remplace sa paint()méthode pour les peindre. Avant de discuter de l'implémentation du proxy, regardez la figure 3, qui est un diagramme de classes du sujet réel du proxy, la javax.swing.ImageIconclasse.

L' javax.swing.Iconinterface, qui définit l'essence des icônes Swing, comprend trois méthodes: paintIcon(), getIconWidth()et getIconHeight(). La ImageIconclasse implémente l' Iconinterface et ajoute ses propres méthodes. Les icônes d'image conservent également une description et une référence à leurs images.

Les proxys d'icônes d'image implémentent l' Iconinterface et conservent une référence à une icône d'image - le sujet réel - comme l'illustre le diagramme de classes de la figure 4.

La ImageIconProxyclasse est répertoriée dans l'exemple 4.

Exemple 4. ImageIconProxy.java

// ImageIconProxy est un proxy (ou substitut) pour une icône. // Le proxy retarde le chargement de l'image jusqu'à la première // image dessinée. Pendant que l'icône charge son image, le // proxy dessine une bordure et le message "Loading image ..." class ImageIconProxy implémente javax.swing.Icon {private Icon realIcon = null; booléen isIconCreated= faux; private String imageName; largeur, hauteur int privé; public ImageIconProxy (String imageName, largeur int, hauteur int) {this.imageName = imageName; this.width = largeur; this.height = hauteur; } public int getIconHeight () {renvoie isIconCreated? hauteur: realIcon.getIconHeight (); } public int getIconWidth () {renvoie isIconCreated realIcon == null? largeur: realIcon.getIconWidth (); } // La méthode paint () du proxy est surchargée pour dessiner une bordure // et un message ("Loading image ...") pendant le chargement de l'image //. Une fois l'image chargée, elle est dessinée. Notez // que le proxy ne charge pas l'image jusqu'à ce que // soit réellement nécessaire. public void paintIcon (Composant final c, Graphiques g, int x, int y) { if (isIconCreated) { realIcon.paintIcon (c, g, x, y); } else { g.drawRect(x, y, largeur-1, hauteur-1); g.drawString ("Chargement de l'image ...", x + 20, y + 20); // L'icône est créée (ce qui signifie que l'image est chargée) // sur un autre thread. synchronized (this) {SwingUtilities.invokeLater (new Runnable () {public void run () {try {// Ralentit le processus de chargement de l'image. Thread.currentThread (). sleep (2000); // Le constructeur ImageIcon crée l'image . realIcon = new ImageIcon (imageName); isIconCreated = true;} catch (InterruptedException ex) {ex.printStackTrace ();} // Repeindre le composant de l'icône après la // création de l'icône. c.repaint (); }} ); }}}}

ImageIconProxyconserve une référence à l'icône réelle avec la realIconvariable membre. La première fois que le proxy est peint, l'icône réelle est créée sur un thread séparé pour permettre au rectangle et à la chaîne d'être peints (les appels à g.drawRect()et g.drawString()ne prennent effet qu'au paintIcon()retour de la méthode). Une fois que l'icône réelle est créée, et par conséquent l'image est chargée, le composant qui affiche l'icône est repeint. La figure 5 montre un diagramme de séquence pour ces événements.

Le diagramme de séquence de la figure 5 est typique de tous les mandataires: les mandataires contrôlent l'accès à leur sujet réel. En raison de ce contrôle, les mandataires instancient souvent leur sujet réel , comme c'est le cas pour le proxy d'icône d'image répertorié dans l'exemple 4. Cette instanciation est l'une des différences entre le modèle Proxy et le modèle Decorator: Les décorateurs créent rarement leurs vrais sujets.

Prise en charge intégrée du JDK pour le modèle de conception Proxy

Le modèle Proxy est l'un des modèles de conception les plus importants car il offre une alternative à l'extension des fonctionnalités avec héritage. Cette alternative est la composition d'objets, où un objet (proxy) transmet les appels de méthode à un objet fermé (sujet réel).

La composition d'objet est préférable à l'héritage car, avec la composition, les objets englobants ne peuvent manipuler leur objet fermé que via l'interface de l'objet fermé, ce qui entraîne un couplage lâche entre les objets. En revanche, avec l'héritage, les classes sont étroitement couplées à leur classe de base car les éléments internes d'une classe de base sont visibles par ses extensions. En raison de cette visibilité, l'héritage est souvent appelé réutilisation en boîte blanche. D'un autre côté, avec la composition, les éléments internes de l'objet englobant ne sont pas visibles pour l'objet inclus (et vice-versa); par conséquent, la composition est souvent appelée réutilisation de la boîte noire. Toutes choses étant égales par ailleurs, la réutilisation de la boîte noire (composition) est préférable à la réutilisation de la boîte blanche (héritage) car un couplage lâche entraîne des systèmes plus malléables et flexibles.

Parce que le modèle Proxy est si important, J2SE 1.3 (Java 2 Platform, Standard Edition) et au-delà le prend directement en charge. Ce soutien comprend trois classes du java.lang.reflectpackage: Proxy, Methodet InvocationHandler. L'exemple 5 montre un exemple simple qui utilise le support JDK pour le modèle Proxy: