JavaRush /Java Blog /Random-IT /Modello di progettazione proxy

Modello di progettazione proxy

Pubblicato nel gruppo Random-IT
Nella programmazione è importante pianificare adeguatamente l'architettura dell'applicazione. Uno strumento indispensabile a questo scopo sono i design pattern. Oggi parleremo di Procura, o in altre parole di Deputato.

Perché hai bisogno di un vice?

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

Esempio 1

Immaginiamo di avere un progetto di grandi dimensioni con un mucchio di vecchio codice, in cui è presente una classe responsabile del download dei report dal database. La classe funziona in modo sincrono, ovvero l'intero sistema è inattivo mentre il database elabora la richiesta. In media, un report viene generato in 30 minuti. Grazie a questa funzionalità, il suo caricamento inizia alle 00:30 e la direzione riceve questo rapporto la mattina. Durante l'analisi è emerso che è necessario ricevere il report immediatamente dopo la sua generazione, ovvero entro un giorno. È impossibile riprogrammare l'orario di inizio poiché il sistema attenderà una risposta dal database. La soluzione è modificare il principio di funzionamento avviando il caricamento e la generazione del report in un thread separato. Questa soluzione consentirà al sistema di funzionare normalmente e la direzione riceverà nuovi report. C'è però un problema: il codice attuale non può essere riscritto, poiché le sue funzioni vengono utilizzate da altre parti del sistema. In questo caso, puoi introdurre una classe proxy intermedia utilizzando il pattern Deputy, che riceverà una richiesta di caricare un report, registrerà l'ora di inizio e avvierà un thread separato. Quando il report verrà generato, il thread completerà il suo lavoro e tutti saranno contenti.

Esempio 2

Il team di sviluppo crea un sito Web di poster. Per ottenere dati sui nuovi eventi, si rivolgono a un servizio di terze parti, la cui interazione viene implementata tramite un'apposita libreria chiusa. Durante lo sviluppo è sorto un problema: un sistema di terze parti aggiorna i dati una volta al giorno e viene inviata una richiesta ogni volta che l'utente aggiorna la pagina. Ciò crea un numero elevato di richieste e il servizio smette di rispondere. La soluzione è memorizzare nella cache la risposta del servizio e fornire ai visitatori il risultato salvato a ogni riavvio, aggiornando questa cache secondo necessità. In questo caso, utilizzare il modello Deputy è un'ottima soluzione senza modificare la funzionalità finale.

Come funziona il modello

Per implementare questo modello, è necessario creare una classe proxy. Implementa un'interfaccia di classe di servizio, simulando il suo comportamento per il codice client. Pertanto, invece dell'oggetto reale, il client interagisce con il suo proxy. In genere, tutte le richieste vengono trasmesse alla classe di servizio, ma con azioni aggiuntive prima o dopo la sua chiamata. In poche parole, questo oggetto proxy è uno strato tra il codice client e l'oggetto di destinazione. Diamo un'occhiata ad un esempio di memorizzazione nella cache di una richiesta da un vecchio disco molto lento. Sia l'orario di un treno elettrico in qualche antica applicazione, il cui principio di funzionamento non può essere modificato. Il disco con la pianificazione aggiornata viene inserito ogni giorno ad un orario prestabilito. Quindi abbiamo:
  1. Interfaccia TimetableTrains.
  2. La classe TimetableElectricTrainsche implementa questa interfaccia.
  3. È attraverso questa classe che il codice client interagisce con il file system del disco.
  4. Classe cliente DisplayTimetable. Il suo metodo printTimetable()utilizza metodi di classe TimetableElectricTrains.
Lo schema è semplice: Modello di progettazione proxy - 2attualmente, ogni volta che viene chiamato un metodo, printTimetable()la classe TimetableElectricTrainsaccede al disco, scarica i dati e li fornisce al client. Questo sistema funziona bene, ma è molto lento. Pertanto, si è deciso di aumentare le prestazioni del sistema aggiungendo un meccanismo di memorizzazione nella cache. Questo può essere fatto utilizzando il pattern Proxy: Modello di progettazione proxy - 3in questo modo la classe DisplayTimetablenon si accorgerà nemmeno che sta interagendo con la classe TimetableElectricTrainsProxye non con quella precedente. La nuova implementazione carica la pianificazione una volta al giorno e, in seguito a richieste ripetute, restituisce l'oggetto già caricato dalla memoria.

Per quali attività è meglio utilizzare Proxy?

Ecco alcune situazioni in cui questo modello tornerà sicuramente utile:
  1. Memorizzazione nella cache.
  2. L'implementazione lazy è anche nota come implementazione lazy. Perché caricare un oggetto tutto in una volta quando puoi caricarlo secondo necessità?
  3. Richieste di registrazione.
  4. Dati provvisori e controlli di accesso.
  5. Avvio di thread di elaborazione parallela.
  6. Registrare o contare la cronologia di una chiamata.
Esistono anche altri casi d'uso. Comprendendo il principio di funzionamento di questo modello, tu stesso puoi trovarne un'applicazione di successo. A prima vista, Deputy fa la stessa cosa di Facade , ma non lo è. Il proxy ha la stessa interfaccia dell'oggetto servizio. Inoltre, non confondere il modello con Decoratore o Adattatore . Il Decoratore fornisce un'interfaccia estesa, mentre l'Adattatore ne fornisce una alternativa.

Vantaggi e svantaggi

  • + Puoi controllare l'accesso all'oggetto del servizio come desideri;
  • + Funzionalità aggiuntive per la gestione del ciclo di vita di un oggetto di servizio;
  • + Funziona senza oggetto di servizio;
  • + Migliora le prestazioni e la sicurezza del codice.
  • - Esiste il rischio di peggioramento delle prestazioni a causa di trattamenti aggiuntivi;
  • - Complica la struttura delle classi del programma.

Modello sostitutivo nella pratica

Implementiamo insieme a te un sistema che legge gli orari dei treni dal disco:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Una classe che implementa l'interfaccia principale:
public class TimetableElectricTrains implements TimetableTrains {

   @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 provi a ottenere gli orari di tutti i treni, il programma legge il file dal disco. Ma questi sono pur sempre fiori. Il file viene letto anche ogni volta che è necessario ottenere l'orario di un solo treno! È positivo che tale codice esista solo in cattivi esempi :) Classe client:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       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;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Proviamo:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Conclusione:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Ora esaminiamo i passaggi di implementazione del nostro modello:
  1. Definire un'interfaccia che consenta di utilizzare un nuovo proxy invece dell'oggetto originale. Nel nostro esempio lo è TimetableTrains.

  2. Crea una classe proxy. Deve contenere un riferimento a un oggetto di servizio (creato in una classe o passato in un costruttore);

    Ecco la nostra classe proxy:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    In questa fase creiamo semplicemente una classe con un riferimento all'oggetto originale e gli passiamo tutte le chiamate.

  3. Implementiamo la logica della classe proxy. Fondamentalmente la chiamata viene sempre reindirizzata all'oggetto originale.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    Il metodo getTimetable()controlla se l'array di pianificazione è memorizzato nella cache in memoria. In caso contrario, emette una richiesta per caricare i dati dal disco, memorizzando il risultato. Se la richiesta è già in esecuzione, restituirà rapidamente un oggetto dalla memoria.

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

    Non puoi farlo. Se dovessi duplicare il codice o eseguire manipolazioni simili, significa che qualcosa è andato storto e devi guardare il problema da una prospettiva diversa. Nel nostro semplice esempio non c'è altro modo, ma nei progetti reali, molto probabilmente, il codice verrà scritto in modo più corretto.

  4. Sostituisci la creazione dell'oggetto originale nel codice client con un oggetto sostitutivo:

    public class DisplayTimetable {
       // Измененная link
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           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]);
           }
       }
    }

    Visita medica

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Ottimo, funziona correttamente.

    Puoi anche considerare una fabbrica che creerà sia l'oggetto originale che un oggetto sostitutivo a seconda di determinate condizioni.

Collegamento utile invece di un punto

  1. Ottimo articolo sui modelli e qualcosa su “Vice”

È tutto per oggi! Sarebbe bello tornare ad apprendere e mettere alla prova le tue nuove conoscenze nella pratica :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION