Prendi il controllo con il modello di progettazione proxy

Un mio amico - un medico, nientemeno - una volta mi disse che aveva convinto un amico a sostenere un esame universitario per lui. Qualcuno che prende il posto di qualcun altro è noto come proxy. Sfortunatamente per il mio amico, il suo procuratore ha bevuto un po 'troppo la sera prima e ha fallito il test.

Nel software, il modello di progettazione Proxy si rivela utile in numerosi contesti. Ad esempio, utilizzando Java XML Pack, si utilizzano proxy per accedere ai servizi Web con JAX-RPC (API Java per chiamate di procedura remota basate su XML). L'esempio 1 mostra come un client accede a un semplice servizio Web Hello World:

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

public class 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 (eccezione ex) {ex.printStackTrace (); }}}

Il codice dell'Esempio 1 è molto simile all'esempio dei servizi Web Hello World incluso con JAX-RPC. Il client ottiene un riferimento al proxy e imposta l'endpoint del proxy (l'URL del servizio Web) con un argomento della riga di comando. Una volta che il client ha un riferimento al proxy, richiama il sayHello()metodo del proxy . Il proxy inoltra la chiamata al metodo al servizio Web, che spesso risiede su una macchina diversa da quella del client.

L'esempio 1 illustra un utilizzo del modello di progettazione proxy: accesso a oggetti remoti. I proxy si dimostrano utili anche per creare risorse costose su richiesta, un proxy virtuale e per controllare l'accesso agli oggetti, un proxy di protezione.

Se hai letto il mio "Decora il tuo codice Java" ( JavaWorld, dicembre 2001), potresti notare delle somiglianze tra i modelli di design Decorator e Proxy. Entrambi i modelli utilizzano un proxy che inoltra le chiamate al metodo a un altro oggetto, noto come soggetto reale. La differenza è che, con il modello Proxy, la relazione tra un proxy e il soggetto reale è tipicamente impostata in fase di compilazione, mentre i decoratori possono essere costruiti in modo ricorsivo in fase di esecuzione. Ma sto superando me stesso.

In questo articolo, per prima cosa introduco il modello Proxy, iniziando con un esempio di proxy per le icone Swing. Concludo con uno sguardo al supporto integrato di JDK per il pattern Proxy.

Nota: nelle prime due puntate di questa colonna - "Stupisci i tuoi amici sviluppatori con modelli di design" (ottobre 2001) e "Decora il tuo codice Java" - ho discusso del motivo Decorator, che è strettamente correlato al modello Proxy, quindi potrebbe voler guardare questi articoli prima di procedere.

Il modello proxy

Proxy: controlla l'accesso a un oggetto con un proxy (noto anche come surrogato o segnaposto).

Le icone di oscillazione, per i motivi discussi nella sezione "Applicabilità proxy" di seguito, rappresentano una scelta eccellente per illustrare il modello proxy. Inizio con una breve introduzione alle icone Swing, seguita da una discussione su un proxy per icone Swing.

Icone dell'oscillazione

Le icone di oscillazione sono piccole immagini utilizzate nei pulsanti, nei menu e nelle barre degli strumenti. È inoltre possibile utilizzare le icone Swing da sole, come illustrato nella Figura 1.

L'applicazione mostrata nella Figura 1 è elencata nell'Esempio 2:

Esempio 2. Icone di oscillazione

import java.awt. *; import java.awt.event. *; import javax.swing. *; // Questa classe verifica un'icona di immagine. la classe pubblica IconTest estende JFrame {stringa statica privata IMAGE_NAME = "mandrill.jpg"; int statico privato FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 268, FRAME_HEIGHT = 286; Icona privata imageIcon = null, imageIconProxy = null; static public void main (String args []) {IconTest app = new IconTest (); app.show (); } public IconTest () {super ("Icon Test"); imageIcon = nuovo ImageIcon (IMAGE_NAME); setBounds (FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); } public void paint (Graphics g) {super.paint (g); Insets insets = getInsets (); imageIcon.paintIcon (this, g, insets.left, insets.top); }}

L'applicazione precedente crea un'icona immagine - un'istanza di javax.swing.ImageIcon- e quindi sovrascrive il paint()metodo per dipingere l'icona.

Proxy immagine-icona swing

L'applicazione mostrata nella Figura 1 è un cattivo utilizzo delle icone delle immagini Swing perché dovresti usare le icone delle immagini solo per le immagini piccole. Questa restrizione esiste perché la creazione di immagini è costosa e le ImageIconistanze creano le proprie immagini quando vengono costruite. Se un'applicazione crea molte immagini di grandi dimensioni contemporaneamente, potrebbe causare un calo significativo delle prestazioni. Inoltre, se l'applicazione non utilizza tutte le sue immagini, è uno spreco crearle in anticipo.

Una soluzione migliore carica le immagini quando diventano necessarie. Per fare ciò, un proxy può creare l'icona reale la prima volta che paintIcon()viene chiamato il metodo del proxy . La Figura 2 mostra un'applicazione che contiene un'icona immagine (a sinistra) e un proxy icona immagine (a destra). L'immagine in alto mostra l'applicazione subito dopo il suo avvio. Poiché le icone delle immagini caricano le proprie immagini quando vengono create, l'immagine di un'icona viene visualizzata non appena si apre la finestra dell'applicazione. Al contrario, il proxy non carica la sua immagine fino a quando non viene disegnata per la prima volta. Fino a quando l'immagine non viene caricata, il proxy disegna un bordo attorno al suo perimetro e visualizza "Caricamento immagine ..." L'immagine in basso nella Figura 2 mostra l'applicazione dopo che il proxy ha caricato la sua immagine.

Ho elencato l'applicazione mostrata nella Figura 2 nell'Esempio 3:

Esempio 3. Proxy dell'icona di oscillazione

import java.awt. *; import java.awt.event. *; import javax.swing. *; // Questa classe testa un proxy virtuale, che è un proxy che // ritarda il caricamento di una risorsa costosa (un'icona) fino a quando quella // risorsa non è necessaria. la classe pubblica VirtualProxyTest estende JFrame {stringa statica privata IMAGE_NAME = "mandrill.jpg"; int statico privato IMAGE_WIDTH = 256, IMAGE_HEIGHT = 256, SPACING = 5, FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 530, FRAME_HEIGHT = 286; Icona privata imageIcon = null, imageIconProxy = null; static public void main (String args []) {VirtualProxyTest app = new VirtualProxyTest (); app.show (); } public VirtualProxyTest () {super ("Virtual Proxy Test"); // Crea un'icona immagine e un proxy icona immagine. imageIcon = nuovo ImageIcon (IMAGE_NAME); imageIconProxy = nuovoImageIconProxy (IMAGE_NAME, IMAGE_WIDTH, IMAGE_HEIGHT); // Imposta i limiti del frame e l'operazione di chiusura // predefinita del frame. setBounds (FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); } public void paint (Graphics g) {super.paint (g); Insets insets = getInsets (); imageIcon.paintIcon (this, g, insets.left, insets.top); imageIconProxy.paintIcon (this, g, insets.left + IMAGE_WIDTH + SPACING, // width insets.top); // altezza } }

L'esempio 3 è quasi identico all'esempio 2, eccetto per l'aggiunta del proxy icona immagine. L'applicazione dell'Esempio 3 crea l'icona e il proxy nel suo costruttore e sovrascrive il suo paint()metodo per dipingerli. Prima di discutere l'implementazione del proxy, guarda la Figura 3, che è un diagramma di classe del vero soggetto del proxy, la javax.swing.ImageIconclasse.

L' javax.swing.Iconinterfaccia, che definisce l'essenza di icone Altalena, include tre metodi: paintIcon(), getIconWidth(), e getIconHeight(). La ImageIconclasse implementa l' Iconinterfaccia e aggiunge metodi propri. Le icone delle immagini mantengono anche una descrizione e un riferimento alle loro immagini.

I proxy icona-immagine implementano l' Iconinterfaccia e mantengono un riferimento a un'icona immagine - il soggetto reale - come illustrato nel diagramma delle classi nella Figura 4.

La ImageIconProxyclasse è elencata nell'Esempio 4.

Esempio 4. ImageIconProxy.java

// ImageIconProxy è un proxy (o surrogato) per un'icona. // Il proxy ritarda il caricamento dell'immagine fino alla prima volta che // viene disegnata l'immagine. Mentre l'icona sta caricando la sua immagine, // proxy disegna un bordo e il messaggio "Caricamento immagine in corso ..." ImageIconProxy implementa javax.swing.Icon {private Icon realIcon = null; boolean isIconCreated= falso; private String imageName; private int larghezza, altezza; public ImageIconProxy (String imageName, int width, int height) {this.imageName = imageName; this.width = larghezza; this.height = altezza; } public int getIconHeight () {return isIconCreated? altezza: realIcon.getIconHeight (); } public int getIconWidth () {return isIconCreated realIcon == null? larghezza: realIcon.getIconWidth (); } // Il metodo paint () del proxy viene sovraccaricato per disegnare un bordo // e un messaggio ("Caricamento immagine ...") durante il caricamento // dell'immagine. Dopo che l'immagine è stata caricata, viene disegnata. Notare // che il proxy non carica l'immagine finché non è // effettivamente necessaria. public void paintIcon (componente finale c, grafica g, int x, int y) { if (isIconCreated) { realIcon.paintIcon (c, g, x, y); } altro { g.drawRect(x, y, larghezza-1, altezza-1); g.drawString ("Caricamento immagine ...", x + 20, y + 20); // L'icona viene creata (significa che l'immagine viene caricata) // su un altro thread. sincronizzato (questo) {SwingUtilities.invokeLater (new Runnable () {public void run () {try {// Rallenta il processo di caricamento dell'immagine. Thread.currentThread (). sleep (2000); // ImageIcon costruttore crea l'immagine . realIcon = new ImageIcon (imageName); isIconCreated = true;} catch (InterructedException ex) {ex.printStackTrace ();} // Ridisegna il componente dell'icona dopo che // l'icona è stata creata. c.repaint (); }} ); }}}}

ImageIconProxymantiene un riferimento all'icona reale con la realIconvariabile membro. La prima volta che il proxy viene disegnato, l'icona reale viene creata su un thread separato per consentire di disegnare il rettangolo e la stringa (le chiamate a g.drawRect()e g.drawString()non hanno effetto finché il paintIcon()metodo non ritorna). Dopo che l'icona reale è stata creata e quindi l'immagine è stata caricata, il componente che visualizza l'icona viene ridipinto. La Figura 5 mostra un diagramma di sequenza per questi eventi.

Il diagramma di sequenza della Figura 5 è tipico di tutti i proxy: i proxy controllano l'accesso al loro vero soggetto. A causa di questo controllo, i proxy spesso creano un'istanza del loro vero soggetto , come nel caso del proxy dell'icona dell'immagine elencato nell'Esempio 4. Quell'istanziazione è una delle differenze tra il modello Proxy e il modello Decorator: I decoratori raramente creano i loro soggetti reali.

Il supporto integrato di JDK per il modello di progettazione Proxy

Il modello proxy è uno dei modelli di progettazione più importanti perché fornisce un'alternativa all'estensione della funzionalità con l'ereditarietà. Questa alternativa è la composizione dell'oggetto, in cui un oggetto (proxy) inoltra le chiamate del metodo a un oggetto racchiuso (soggetto reale).

La composizione degli oggetti è preferibile all'ereditarietà perché, con la composizione, gli oggetti racchiusi possono manipolare il loro oggetto racchiuso solo attraverso l'interfaccia dell'oggetto racchiuso, il che si traduce in un accoppiamento libero tra gli oggetti. Al contrario, con l'ereditarietà, le classi sono strettamente collegate alla loro classe di base perché le parti interne di una classe di base sono visibili alle sue estensioni. A causa di questa visibilità, l'ereditarietà viene spesso definita riutilizzo della scatola bianca. D'altra parte, con la composizione, gli interni dell'oggetto racchiuso non sono visibili all'oggetto racchiuso (e viceversa); pertanto, la composizione viene spesso definita riutilizzo della scatola nera. A parità di condizioni, il riutilizzo (composizione) della scatola nera è preferibile al riutilizzo della scatola bianca (eredità) perché l'accoppiamento sciolto si traduce in sistemi più malleabili e flessibili.

Poiché il modello Proxy è così importante, J2SE 1.3 (Java 2 Platform, Standard Edition) e oltre lo supporta direttamente. Tale sostegno coinvolge tre classi del java.lang.reflectpacchetto: Proxy, Method, e InvocationHandler. L'esempio 5 mostra un semplice esempio che utilizza il supporto JDK per il modello Proxy: