Design Pattern in Java: Proxy
Analizziamo un altro utile Design Pattern in java: il Proxy Pattern.
Il proxy pattern nasce principalmente per risolvere problematiche prestazionali legate all’accesso ad un oggetto che richiede tempi importanti per la creazione o semplicemente per essere raggiunto.
Il pattern prevede la realizzazione di un oggetto proxy che viene usato al posto dell’oggetto reale e che quindi deve avere necessariamente la stessa “forma” dell’oggetto che sostituisce. Il proxy poi si preoccuperà di contattare l’oggetto reale, nella modalità e nel momento più opportuni.Approfondiamo la questione esaminando un esempio.
Si ha la necessità di sviluppare un oggetto ImageHandler che permetta di ricavare diverse informazioni da un file immagine. In particolare la classe deve essere in grado di fornire le dimensioni dell’immagine, espresse in pixel, attraverso i metodi getWidth() e getHeight(), di fornire il valore di un determinato pixel, attraverso il metodo getPixel(int x, int y) che richiede come parametri le coordinate del pixel e attraverso il metodo getContent() fornisca l’intera immagine come array di bytes.
Infine il metodo getFileName() restituisce il nome del file immagine come stringa.
Supponiamo che l’accesso alle immagini in questione sia alquanto dispendioso in termini di tempo, magari perchè molto distanti. E’ evidente che l’apertura del file solo per conoscerne un pixel è inutilmente costoso, specie se spesso viene richiesto sempre lo stesso sottoinsieme di pixels. In tal caso l’oggetto ProxyImageHandler fungerà da caché proxy, immagazzinando i pixel di cui si conosce già il valore, per un precedente accesso e restituendo tali valori senza dover aprire nuovamente il file immagine.
Per alcune operazioni poi, come getFileName(), non è necessario istanziare l’oggetto reale, caricando l’immagine e perdendo inutilmente tempo. In questi casi il proxy funge da virtual proxy posponendo l’istanzazione del RealSubject a quando realmente indispensabile.
Esaminiamo il diagramma sopra, in dettaglio. ProxyImageHandler e RealImageHandler devono avere, come dicevamo, la stessa forma, per questo implementano la stessa Interfaccia(Subject): ImageHandler.
Il Proxy quindi gestisce l’accesso al RealSubject, in questo caso RealImageHandler, secondo politiche di ottimizzazione e lo sostiutuisce completamente alla vista degli utilizzatori.
Dal punto di vista dell’implementazione del codice, la cosa è abbastanza semplice. Si scrive la classe che rappresenta l’interfaccia Subject, che entrambi gli oggetti devono estendere:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public abstract class ImageHandler { protected String fileName; public ImageHandler(String fileName) { this.fileName = fileName; } public String getFileName() { return fileName; } public abstract bytes[] getContent(); public abstract byte getPixel(int x, int y); public abstract int getWidth(); public abstract int getHeight(); } |
Il RealSubject, in questo esempio RealImageHandler, estenderà il Subject e implementerà la logica di caricamento dell’immagine direttamente nel costruttore. I vari metodi, accedendo direttamente all’oggetto immagine restituiranno i valori richiesti (altezza, larghezza, valore di un pixel).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class RealImageHandler extends ImageHandler{ private byte[] content; public RealImageHandler(String fileName) { super(fileName); /*.... codice per il caricamento dell'immagine ....*/ } public bytes[] getContent(){ return content; } public byte getPixel(int x, int y){ /*.... codice per il calcolo del pixel ....*/ } public int getWidth(){ /*.... codice per il calcolo della larghezza ....*/ } public int getHeight(){ /*.... codice per il calcolo dell'altezza ....*/ } } |
Anche il Proxy, dovendo sostituire in tutto il RealSubject, deve estendere la classe Subject e soprattutto mantenere una istanza del RealSubject, che pero’ non viene creata solo al momento in cui diventa indispensabile caricare in memoria il contenuto del file, quindi quando si richiede l’intero contenuto o il valore di un singolo byte.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class ProxyImageHandler extends ImageHandler{ private RealImageHandler realHandler; public bytes[] getContent(){ if (realHandler == null) realHandler = new RealImageHandler(); return realHandler.getContent(); } public byte getPixel(int x, int y){ if (realHandler == null) realHandler = new RealImageHandler(); return realHandler.getPixel(x,y); } public int getWidth(){ if (realHandler == null) realHandler = new RealImageHandler(); return realHandler.getWidth(); } public int getHeight(){ if (realHandler == null) realHandler = new RealImageHandler(); return realHandler.getHeight(); } } |
Gli scenari di applicazione di questo pattern sono vari. Ad esempio RMI (Remote Method Invocation) che utilizza il Proxy Pattern per l’interfacciamento ad oggetti remoti. In questo caso le classi Proxy sono dette “stub”.
Un altro campo di applicazione classico di questo pattern è la sicurezza. Il Security Proxy o Protection Proxy puo’ sostituirsi a sistemi “legacy” che non abbiano una adeguata politica di sicurezza, per evitare che l’utilizzatore acceda direttamente ad essi e implementando lo strato di protezione necessaria.