CodeGym /Java blogg /Slumpmässig /Proxy designmönster
John Squirrels
Nivå
San Francisco

Proxy designmönster

Publicerad i gruppen
Vid programmering är det viktigt att planera din applikations arkitektur korrekt. Designmönster är ett oumbärligt sätt att åstadkomma detta. I dag ska vi prata om fullmakter.

Varför behöver du en proxy?

Detta mönster hjälper till att lösa problem i samband med kontrollerad åtkomst till ett objekt. Du kanske frågar "Varför behöver vi kontrollerad åtkomst?" Låt oss titta på ett par situationer som hjälper dig att ta reda på vad som är vad.

Exempel 1

Föreställ dig att vi har ett stort projekt med en massa gammal kod, där det finns en klass som ansvarar för att exportera rapporter från en databas. Klassen arbetar synkront. Det vill säga att hela systemet är inaktivt medan databasen bearbetar begäran. I genomsnitt tar det 30 minuter att generera en rapport. Följaktligen startar exportprocessen klockan 12:30 och ledningen får rapporten på morgonen. En granskning visade att det vore bättre att omedelbart kunna få rapporten under normal kontorstid. Starttiden kan inte skjutas upp och systemet kan inte blockera medan det väntar på svar från databasen. Lösningen är att ändra hur systemet fungerar, generera och exportera rapporten på en separat tråd. Denna lösning låter systemet fungera som vanligt och ledningen kommer att få färska rapporter. Dock, det finns ett problem: den aktuella koden kan inte skrivas om, eftersom andra delar av systemet använder dess funktionalitet. I det här fallet kan vi använda proxymönstret för att introducera en mellanliggande proxyklass som tar emot förfrågningar om att exportera rapporter, logga starttiden och starta en separat tråd. När rapporten väl är genererad avslutas tråden och alla är nöjda.

Exempel 2

Ett utvecklingsteam skapar en evenemangswebbplats. För att få data om nya händelser frågar teamet en tredjepartstjänst. Ett särskilt privat bibliotek underlättar interaktionen med tjänsten. Under utvecklingen upptäcks ett problem: tredjepartssystemet uppdaterar sina data en gång om dagen, men en begäran skickas till det varje gång en användare uppdaterar en sida. Detta skapar ett stort antal förfrågningar och tjänsten slutar svara. Lösningen är att cachelagra tjänstens svar och returnera det cachade resultatet till besökarna när sidor laddas om, uppdatera cachen efter behov. I det här fallet är proxydesignmönstret en utmärkt lösning som inte ändrar den befintliga funktionaliteten.

Principen bakom designmönstret

För att implementera detta mönster måste du skapa en proxyklass. Den implementerar gränssnittet för tjänsteklassen och efterliknar dess beteende för klientkod. På detta sätt interagerar klienten med en proxy istället för det verkliga objektet. Som regel skickas alla förfrågningar vidare till serviceklassen, men med ytterligare åtgärder före eller efter. Enkelt uttryckt är en proxy ett lager mellan klientkoden och målobjektet. Tänk på exemplet med cachning av frågeresultat från en gammal och mycket långsam hårddisk. Anta att vi pratar om en tidtabell för elektriska tåg i någon gammal app vars logik inte kan ändras. En diskett med uppdaterad tidtabell sätts in varje dag vid en fast tidpunkt. Så vi har:
  1. TrainTimetablegränssnitt.
  2. ElectricTrainTimetable, som implementerar detta gränssnitt.
  3. Klientkoden interagerar med filsystemet genom denna klass.
  4. TimetableDisplaykundklass. Dess printTimetable()metod använder klassens metoder ElectricTrainTimetable.
Diagrammet är enkelt: Proxydesignmönster: - 2För närvarande, med varje anrop av printTimetable()metoden, ElectricTrainTimetablekommer klassen åt disken, laddar data och presenterar den för klienten. Systemet fungerar okej, men det är väldigt långsamt. Som ett resultat togs beslutet att öka systemets prestanda genom att lägga till en cachningsmekanism. Detta kan göras med hjälp av proxymönstret: Proxydesignmönster: - 3Således TimetableDisplaymärker klassen inte ens att den interagerar med ElectricTrainTimetableProxyklassen istället för den gamla klassen. Den nya implementeringen laddar tidtabellen en gång om dagen. För upprepade förfrågningar returnerar den det tidigare laddade objektet från minnet.

Vilka uppgifter är bäst för en proxy?

Här är några situationer där det här mönstret definitivt kommer att vara användbart:
  1. Cachning
  2. Fördröjd eller lat initialisering Varför ladda ett objekt direkt om du kan ladda det efter behov?
  3. Loggningsförfrågningar
  4. Mellanliggande verifiering av data och åtkomst
  5. Startar arbetartrådar
  6. Spela in åtkomst till ett objekt
Och det finns även andra användningsfall. Genom att förstå principen bakom detta mönster kan du identifiera situationer där det kan tillämpas framgångsrikt. Vid första anblicken gör en proxy samma sak som en fasad , men så är inte fallet. En proxy har samma gränssnitt som serviceobjektet. Blanda inte heller ihop det här mönstret med dekorations- eller adaptermönstren . En dekoratör ger ett utökat gränssnitt och en adapter ger ett alternativt gränssnitt.

Fördelar och nackdelar

  • + Du kan styra åtkomsten till serviceobjektet hur du vill
  • + Ytterligare förmågor relaterade till att hantera serviceobjektets livscykel
  • + Det fungerar utan ett serviceobjekt
  • + Det förbättrar prestanda och kodsäkerhet.
  • – Det finns en risk att prestandan kan bli sämre på grund av ytterligare förfrågningar
  • – Det gör klasshierarkin mer komplicerad

Proxymönstret i praktiken

Låt oss implementera ett system som läser tågtidtabeller från en hårddisk:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Här är klassen som implementerar huvudgränssnittet:

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 "";
   }
}
Varje gång du får tågtidtabellen läser programmet en fil från disken. Men det är bara början på våra problem. Hela filen läses varje gång du får tidtabellen för även ett enda tåg! Det är bra att sådan kod bara finns i exempel på vad man inte ska göra :) Klientklass:

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]);
       }
   }
}
Exempelfil:

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
Låt oss testa det:

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

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
Låt oss nu gå igenom stegen som krävs för att introducera vårt mönster:
  1. Definiera ett gränssnitt som tillåter användning av en proxy istället för det ursprungliga objektet. I vårt exempel är detta TrainTimetable.

  2. Skapa proxyklassen. Det bör ha en referens till serviceobjektet (skapa det i klassen eller skicka till konstruktorn).

    Här är vår proxyklass:

    
    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;
       }
    }
    

    I det här skedet skapar vi helt enkelt en klass med en referens till det ursprungliga objektet och vidarebefordrar alla anrop till den.

  3. Låt oss implementera logiken i proxyklassen. I princip omdirigeras anrop alltid till det ursprungliga objektet.

    
    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;
       }
    }
    

    Den getTimetable()kontrollerar om tidtabellsarrayen har cachelagrats i minnet. Om inte, skickar den en begäran om att ladda data från disken och sparar resultatet. Om tidtabellen redan har begärts, returnerar den snabbt objektet från minnet.

    Tack vare sin enkla funktionalitet behövde getTrainDepartureTime()-metoden inte omdirigeras till det ursprungliga objektet. Vi har helt enkelt duplicerat dess funktionalitet i en ny metod.

    Gör inte det här. Om du måste duplicera koden eller göra något liknande, gick något fel, och du måste titta på problemet igen från en annan vinkel. I vårt enkla exempel hade vi inget annat alternativ. Men i riktiga projekt kommer koden med största sannolikhet att skrivas mer korrekt.

  4. Skapa ett proxyobjekt i klientkoden istället för originalobjektet:

    
    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]);
           }
       }
    }
    

    Kolla upp

    
    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
    

    Jättebra, det fungerar korrekt.

    Du kan också överväga alternativet för en fabrik som skapar både ett originalobjekt och ett proxyobjekt, beroende på vissa förutsättningar.

Innan vi säger hejdå, här är en användbar länk

Det är allt för idag! Det skulle inte vara en dum idé att gå tillbaka till lektionerna och prova dina nya kunskaper i praktiken :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION