CodeGym /Java Blog /Random-IT /Modello di progettazione proxy
John Squirrels
Livello 41
San Francisco

Modello di progettazione proxy

Pubblicato nel gruppo Random-IT
Nella programmazione, è importante pianificare correttamente l'architettura dell'applicazione. I modelli di progettazione sono un modo indispensabile per raggiungere questo obiettivo. Oggi parliamo di proxy.

Perché hai bisogno di un proxy?

Questo modello aiuta a risolvere i problemi associati all'accesso controllato a un oggetto. Potresti chiedere: "Perché abbiamo bisogno di un accesso controllato?" Diamo un'occhiata a un paio di situazioni che ti aiuteranno a capire cosa è cosa.

Esempio 1

Immagina di avere un grande progetto con un mucchio di vecchio codice, dove c'è una classe responsabile dell'esportazione dei report da un database. La classe funziona in modo sincrono. Cioè, l'intero sistema è inattivo mentre il database elabora la richiesta. In media, sono necessari 30 minuti per generare un rapporto. Di conseguenza, il processo di esportazione inizia alle 00:30 e la direzione riceve il rapporto al mattino. Un audit ha rivelato che sarebbe stato meglio poter ricevere immediatamente il rapporto durante il normale orario lavorativo. L'ora di inizio non può essere posticipata e il sistema non può bloccarsi mentre attende una risposta dal database. La soluzione è cambiare il funzionamento del sistema, generando ed esportando il report su un thread separato. Questa soluzione consentirà al sistema di funzionare normalmente e la direzione riceverà nuovi report. Tuttavia, c'è un problema: il codice corrente non può essere riscritto, poiché altre parti del sistema utilizzano la sua funzionalità. In questo caso, possiamo utilizzare il modello proxy per introdurre una classe proxy intermedia che riceverà le richieste di esportazione dei report, registrerà l'ora di inizio e avvierà un thread separato. Una volta generato il rapporto, il thread termina e tutti sono contenti.

Esempio 2

Un team di sviluppo sta creando un sito Web di eventi. Per ottenere dati su nuovi eventi, il team interroga un servizio di terze parti. Una speciale biblioteca privata facilita l'interazione con il servizio. Durante lo sviluppo viene scoperto un problema: il sistema di terze parti aggiorna i suoi dati una volta al giorno, ma gli viene inviata una richiesta ogni volta che un utente aggiorna una pagina. Ciò crea un numero elevato di richieste e il servizio smette di rispondere. La soluzione è memorizzare nella cache la risposta del servizio e restituire il risultato memorizzato nella cache ai visitatori mentre le pagine vengono ricaricate, aggiornando la cache secondo necessità. In questo caso, il proxy design pattern è un'ottima soluzione che non modifica le funzionalità esistenti.

Il principio alla base del modello di progettazione

Per implementare questo modello, è necessario creare una classe proxy. Implementa l'interfaccia della classe di servizio, imitandone il comportamento per il codice client. In questo modo, il client interagisce con un proxy invece che con l'oggetto reale. Di norma, tutte le richieste vengono trasferite alla classe di servizio, ma con azioni aggiuntive prima o dopo. In poche parole, un proxy è uno strato tra il codice client e l'oggetto di destinazione. Si consideri l'esempio della memorizzazione nella cache dei risultati delle query da un disco rigido vecchio e molto lento. Supponiamo che stiamo parlando di un orario per i treni elettrici in qualche antica app la cui logica non può essere cambiata. Ogni giorno ad un'ora fissa viene inserito un dischetto con l'orario aggiornato. Quindi, abbiamo:
  1. TrainTimetableinterfaccia.
  2. ElectricTrainTimetable, che implementa questa interfaccia.
  3. Il codice client interagisce con il file system attraverso questa classe.
  4. TimetableDisplayclasse cliente. Il suo printTimetable()metodo utilizza i metodi della ElectricTrainTimetableclasse.
Il diagramma è semplice: Modello di progettazione proxy: - 2attualmente, ad ogni chiamata del printTimetable()metodo, la ElectricTrainTimetableclasse accede al disco, carica i dati e li presenta al client. Il sistema funziona bene, ma è molto lento. Di conseguenza, è stata presa la decisione di aumentare le prestazioni del sistema aggiungendo un meccanismo di memorizzazione nella cache. Questo può essere fatto usando il proxy pattern: Modello di progettazione proxy: - 3così, la TimetableDisplayclasse non si accorge nemmeno che sta interagendo con la ElectricTrainTimetableProxyclasse invece che con la vecchia classe. La nuova implementazione carica l'orario una volta al giorno. Per le richieste ripetute, restituisce dalla memoria l'oggetto precedentemente caricato.

Quali compiti sono i migliori per un proxy?

Ecco alcune situazioni in cui questo modello tornerà sicuramente utile:
  1. Cache
  2. Inizializzazione ritardata o pigra Perché caricare subito un oggetto se puoi caricarlo secondo necessità?
  3. Richieste di registrazione
  4. Verifica intermedia dei dati e dell'accesso
  5. Avvio di thread di lavoro
  6. Registrazione dell'accesso a un oggetto
E ci sono anche altri casi d'uso. Comprendendo il principio alla base di questo modello, puoi identificare le situazioni in cui può essere applicato con successo. A prima vista, un proxy fa la stessa cosa di una facciata , ma non è così. Un proxy ha la stessa interfaccia dell'oggetto servizio. Inoltre, non confondere questo modello con i modelli decoratore o adattatore . Un decoratore fornisce un'interfaccia estesa e un adattatore fornisce un'interfaccia alternativa.

Vantaggi e svantaggi

  • + Puoi controllare l'accesso all'oggetto di servizio come preferisci
  • + Abilità aggiuntive legate alla gestione del ciclo di vita dell'oggetto servizio
  • + Funziona senza un oggetto di servizio
  • + Migliora le prestazioni e la sicurezza del codice.
  • - Esiste il rischio che le prestazioni peggiorino a causa di ulteriori richieste
  • - Rende la gerarchia delle classi più complicata

Il modello proxy in pratica

Implementiamo un sistema che legge gli orari dei treni da un hard disk:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Ecco la classe che implementa l'interfaccia principale:

public class ElectricTrainTimetable implements TrainTimetable {

   @Override
   public String[] getTimetable() {
       ArrayList<String> list = new ArrayList<>();
       try {
           Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               list.add(line);
           }
       } catch (IOException e) {
           System.err.println("Error:  " + e);
       }
       return list.toArray(new String[list.size()]);
   }

   @Override
   public String getTrainDepartureTime(String trainId) {
       String[] timetable = getTimetable();
       for (int i = 0; i < timetable.length; i++) {
           if (timetable[i].startsWith(trainId+";")) return timetable[i];
       }
       return "";
   }
}
Ogni volta che ottieni l'orario del treno, il programma legge un file dal disco. Ma questo è solo l'inizio dei nostri problemi. L'intero file viene letto ogni volta che ottieni l'orario anche di un solo treno! È positivo che tale codice esista solo in esempi di cosa non fare :) Classe client:

public class TimetableDisplay {
   private TrainTimetable trainTimetable = new ElectricTrainTimetable();

   public void printTimetable() {
       String[] timetable = trainTimetable.getTimetable();
       String[] tmpArr;
       System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
       for (int i = 0; i < timetable.length; i++) {
           tmpArr = timetable[i].split(";");
           System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
       }
   }
}
File di esempio:

9B-6854;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
Proviamolo:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Produzione:

Train  From  To  Departure time  Arrival time  Travel time
9B-6854  London  Prague  13:43  21:15  07:32
BA-1404  Paris  Graz  14:25  21:25  07:00
9B-8710  Prague  Vienna  04:48  08:49  04:01
9B-8122  Prague  Graz  04:48  08:49  04:01
Ora esaminiamo i passaggi necessari per introdurre il nostro modello:
  1. Definire un'interfaccia che consenta l'uso di un proxy invece dell'oggetto originale. Nel nostro esempio, questo è TrainTimetable.

  2. Crea la classe proxy. Dovrebbe avere un riferimento all'oggetto servizio (crearlo nella classe o passarlo al costruttore).

    Ecco la nostra classe proxy:

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
      
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return trainTimetable.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return trainTimetable.getTrainDepartureTime(trainId);
       }
      
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    A questo punto, stiamo semplicemente creando una classe con un riferimento all'oggetto originale e inoltrandogli tutte le chiamate.

  3. Implementiamo la logica della classe proxy. Fondamentalmente, le chiamate vengono sempre reindirizzate all'oggetto originale.

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           for (int i = 0; i < timetableCache.length; i++) {
               if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    Controlla getTimetable()se l'array schedule è stato memorizzato nella cache. In caso contrario, invia una richiesta per caricare i dati dal disco e salva il risultato. Se l'orario è già stato richiesto, restituisce velocemente l'oggetto dalla memoria.

    Grazie alla sua semplice funzionalità, il metodo getTrainDepartureTime() non doveva essere reindirizzato all'oggetto originale. Abbiamo semplicemente duplicato la sua funzionalità in un nuovo metodo.

    Non farlo. Se devi duplicare il codice o fare qualcosa di simile, qualcosa è andato storto e devi guardare di nuovo il problema da una prospettiva diversa. Nel nostro semplice esempio, non avevamo altra scelta. Ma nei progetti reali, molto probabilmente il codice verrà scritto in modo più corretto.

  4. Nel codice client, crea un oggetto proxy invece dell'oggetto originale:

    
    public class TimetableDisplay {
       // Changed reference
       private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy();
    
       public void printTimetable() {
           String[] timetable = trainTimetable.getTimetable();
           String[] tmpArr;
           System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
           for (int i = 0; i < timetable.length; i++) {
               tmpArr = timetable[i].split(";");
               System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
           }
       }
    }
    

    Controllo

    
    Train  From  To  Departure time  Arrival time  Travel time
    9B-6854  London  Prague  13:43  21:15  07:32
    BA-1404  Paris  Graz  14:25  21:25  07:00
    9B-8710  Prague  Vienna  04:48  08:49  04:01
    9B-8122  Prague  Graz  04:48  08:49  04:01
    

    Ottimo, funziona correttamente.

    Potresti anche considerare l'opzione di una fabbrica che crea sia un oggetto originale che un oggetto proxy, a seconda di determinate condizioni.

Prima di salutarci, ecco un link utile

È tutto per oggi! Non sarebbe una cattiva idea tornare alle lezioni e mettere alla prova le tue nuove conoscenze in pratica :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION