CodeGym /Java blog /Véletlen /Proxy tervezési minta
John Squirrels
Szint
San Francisco

Proxy tervezési minta

Megjelent a csoportban
A programozás során fontos az alkalmazás architektúráját helyesen megtervezni. A tervezési minták nélkülözhetetlen módja ennek. Ma beszéljünk a proxy-król.

Miért van szüksége proxyra?

Ez a minta segít megoldani az objektumokhoz való ellenőrzött hozzáféréssel kapcsolatos problémákat. Felteheti a kérdést: "Miért van szükségünk ellenőrzött hozzáférésre?" Nézzünk meg néhány olyan helyzetet, amelyek segítenek kitalálni, mi is az.

1. példa

Képzeljük el, hogy van egy nagy projektünk egy csomó régi kóddal, ahol van egy osztály, amely a jelentések adatbázisból történő exportálásáért felelős. Az osztály szinkronban működik. Vagyis az egész rendszer tétlen, amíg az adatbázis feldolgozza a kérést. Átlagosan 30 percet vesz igénybe egy jelentés elkészítése. Ennek megfelelően az export folyamat 12:30-kor kezdődik, és a menedzsment reggel kapja meg a jelentést. Az ellenőrzés során kiderült, hogy jobb lenne, ha a jelentést a szokásos munkaidőben azonnal megkaphatnánk. A kezdési időpont nem halasztható el, és a rendszer nem tud blokkolni, amíg az adatbázis válaszára vár. A megoldás a rendszer működésének megváltoztatása, külön szálon generálva és exportálva a jelentést. Ezzel a megoldással a rendszer a megszokott módon működhet, a vezetőség pedig friss jelentéseket kap. Azonban, egy probléma van: az aktuális kódot nem lehet átírni, mivel a rendszer más részei használják a funkcióit. Ebben az esetben a proxy mintával bevezethetünk egy köztes proxyosztályt, amely megkapja a jelentések exportálására vonatkozó kéréseket, naplózza a kezdési időpontot, és elindít egy külön szálat. A jelentés létrehozása után a szál véget ér, és mindenki elégedett.

2. példa

Egy fejlesztőcsapat rendezvény weboldalt készít. Az új eseményekre vonatkozó adatok beszerzéséhez a csapat lekérdez egy harmadik féltől származó szolgáltatást. A szolgáltatással való interakciót egy speciális magánkönyvtár segíti. A fejlesztés során egy probléma fedezhető fel: a harmadik féltől származó rendszer naponta egyszer frissíti adatait, de minden alkalommal, amikor egy felhasználó frissít egy oldalt, kérést küldenek neki. Ez nagyszámú kérést hoz létre, és a szolgáltatás nem válaszol. A megoldás az, hogy gyorsítótárazza a szolgáltatás válaszát, és a gyorsítótárazott eredményt visszaküldi a látogatóknak az oldalak újratöltésekor, szükség szerint frissítve a gyorsítótárat. Ebben az esetben a proxy tervezési minta kiváló megoldás, amely nem változtat a meglévő funkcionalitáson.

A tervezési minta alapelve

Ennek a mintának a megvalósításához létre kell hoznia egy proxyosztályt. Megvalósítja a szolgáltatási osztály interfészét, utánozva a viselkedését a kliens kódhoz. Ily módon az ügyfél a valódi objektum helyett egy proxyval lép kapcsolatba. Általános szabály, hogy minden kérés továbbadódik a szolgáltatási osztálynak, de előtte vagy utána további műveletekkel. Egyszerűen fogalmazva, a proxy egy réteg az ügyfélkód és a célobjektum között. Tekintsük a régi és nagyon lassú merevlemezről származó lekérdezési eredmények gyorsítótárazásának példáját. Tegyük fel, hogy elektromos vonatok menetrendjéről beszélünk valami ősi alkalmazásban, amelynek logikája nem változtatható meg. A frissített menetrenddel ellátott lemez minden nap meghatározott időpontban kerül behelyezésre. Tehát nálunk van:
  1. TrainTimetablefelület.
  2. ElectricTrainTimetable, amely ezt a felületet valósítja meg.
  3. Az ügyfélkód ezen az osztályon keresztül kommunikál a fájlrendszerrel.
  4. TimetableDisplayügyfél osztály. Metódusa printTimetable()az osztály metódusait használja ElectricTrainTimetable.
A diagram egyszerű: Proxy tervezési minta: - 2Jelenleg a metódus minden hívásával printTimetable()az ElectricTrainTimetableosztály hozzáfér a lemezhez, betölti az adatokat, és bemutatja a kliensnek. A rendszer jól működik, de nagyon lassú. Ennek eredményeként az a döntés született, hogy gyorsítótárazási mechanizmus hozzáadásával növeljük a rendszer teljesítményét. Ez megtehető a proxy mintával: Proxy tervezési minta: - 3Így az osztály észre sem veszi, hogy a régi osztály helyett TimetableDisplayaz osztállyal kommunikál . ElectricTrainTimetableProxyAz új megvalósítás naponta egyszer tölti be a menetrendet. Ismételt kérések esetén a korábban betöltött objektumot adja vissza a memóriából.

Milyen feladatok a legjobbak egy proxy számára?

Íme néhány olyan helyzet, amikor ez a minta biztosan hasznos lesz:
  1. Gyorsítótárazás
  2. Késleltetett vagy lusta inicializálás Miért töltsünk be egy objektumot azonnal, ha szükség szerint betölthetjük?
  3. Naplózási kérések
  4. Az adatok és hozzáférés közbenső ellenőrzése
  5. Munkavállalói szálak indítása
  6. Objektumhoz való hozzáférés rögzítése
És vannak más felhasználási esetek is. A minta mögött rejlő elv megértésével azonosíthatja azokat a helyzeteket, ahol sikeresen alkalmazható. Első pillantásra a proxy ugyanazt teszi, mint egy homlokzat , de ez nem így van. A proxy ugyanazzal a felülettel rendelkezik, mint a szolgáltatásobjektum. Ezenkívül ne keverje össze ezt a mintát a dekorátor vagy az adapter mintákkal. A dekorátor kiterjesztett interfészt, az adapter pedig alternatív interfészt biztosít.

Előnyök és hátrányok

  • + A szolgáltatásobjektumhoz való hozzáférést tetszés szerint szabályozhatja
  • + A szolgáltatásobjektum életciklusának kezeléséhez kapcsolódó további képességek
  • + Szolgáltatásobjektum nélkül működik
  • + Javítja a teljesítményt és a kódbiztonságot.
  • - Fennáll annak a veszélye, hogy a teljesítmény romolhat a további kérések miatt
  • - Bonyolítja az osztályhierarchiát

A proxy minta a gyakorlatban

Valósítsunk meg egy rendszert, amely merevlemezről olvassa be a vonatok menetrendjét:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Íme az osztály, amely megvalósítja a fő felületet:

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 "";
   }
}
Minden alkalommal, amikor megkapja a vonat menetrendjét, a program beolvas egy fájlt a lemezről. De ez csak a kezdete a bajainknak. A teljes fájlt minden alkalommal elolvassa, amikor akár egyetlen vonat menetrendjét is megkapja! Még jó, hogy ilyen kód csak példákban létezik, hogy mit ne tegyünk :) Kliens osztály:

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]);
       }
   }
}
Példafájl:

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
Teszteljük:

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

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
Most pedig nézzük meg a mintánk bevezetéséhez szükséges lépéseket:
  1. Határozzon meg egy interfészt, amely lehetővé teszi proxy használatát az eredeti objektum helyett. Példánkban ez TrainTimetable.

  2. Hozza létre a proxy osztályt. Hivatkozást kell tartalmaznia a szolgáltatás objektumra (hozza létre az osztályban vagy adja át a konstruktornak).

    Íme a proxy osztályunk:

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

    Ebben a szakaszban egyszerűen létrehozunk egy osztályt az eredeti objektumra való hivatkozással, és az összes hívást arra továbbítjuk.

  3. Valósítsuk meg a proxy osztály logikáját. Alapvetően a hívások mindig az eredeti objektumra kerülnek átirányításra.

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

    Ellenőrzi getTimetable(), hogy az ütemezési tömb gyorsítótárban van-e a memóriában. Ha nem, akkor kérelmet küld az adatok lemezről való betöltésére, és elmenti az eredményt. Ha az órarendet már kérték, akkor gyorsan visszaadja az objektumot a memóriából.

    Egyszerű működésének köszönhetően a getTrainDepartureTime() metódust nem kellett átirányítani az eredeti objektumra. Egyszerűen megkettőztük a funkcióját egy új módszerrel.

    Ne csináld ezt. Ha meg kell másolnia a kódot, vagy valami hasonlót kell tennie, akkor valami elromlott, és újra meg kell vizsgálnia a problémát egy másik szemszögből. Egyszerű példánkban nem volt más lehetőségünk. De valós projektekben a kód valószínűleg helyesebben lesz megírva.

  4. Az ügyfélkódban hozzon létre egy proxy objektumot az eredeti objektum helyett:

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

    Jelölje be

    
    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
    

    Remek, korrektül működik.

    Megfontolhatja egy olyan gyár lehetőségét is, amely bizonyos feltételektől függően eredeti objektumot és proxy objektumot is létrehoz.

Mielőtt elköszönnénk, itt egy hasznos link

Ez minden mára! Nem lenne rossz ötlet visszatérni az órákra és a gyakorlatban is kipróbálni az új ismereteket :)
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION