CodeGym /בלוג Java /Random-HE /דפוס עיצוב פרוקסי
John Squirrels
רָמָה
San Francisco

דפוס עיצוב פרוקסי

פורסם בקבוצה
בתכנות, חשוב לתכנן נכון את ארכיטקטורת האפליקציה שלך. דפוסי עיצוב הם דרך הכרחית להשיג זאת. היום בואו נדבר על פרוקסי.

למה אתה צריך פרוקסי?

דפוס זה עוזר לפתור בעיות הקשורות לגישה מבוקרת לאובייקט. אתה עשוי לשאול, "למה אנחנו צריכים גישה מבוקרת?" בואו נסתכל על כמה מצבים שיעזרו לכם להבין מה זה מה.

דוגמה 1

תארו לעצמכם שיש לנו פרויקט גדול עם חבורה של קוד ישן, שבו יש כיתה שאחראית על ייצוא דוחות ממסד נתונים. הכיתה עובדת באופן סינכרוני. כלומר, המערכת כולה פעילה בזמן שבסיס הנתונים מעבד את הבקשה. בממוצע, לוקח 30 דקות להפיק דוח. בהתאם לכך, תהליך הייצוא מתחיל בשעה 12:30, וההנהלה מקבלת את הדיווח בבוקר. בדיקה העלתה כי עדיף לקבל מיד את הדיווח בשעות העבודה הרגילות. לא ניתן לדחות את שעת ההתחלה, והמערכת לא יכולה לחסום בזמן שהיא ממתינה לתגובה ממסד הנתונים. הפתרון הוא לשנות את אופן פעולת המערכת, הפקה וייצוא של הדוח בשרשור נפרד. פתרון זה יאפשר למערכת לעבוד כרגיל, וההנהלה תקבל דיווחים טריים. עם זאת, יש בעיה: לא ניתן לשכתב את הקוד הנוכחי, מכיוון שחלקים אחרים של המערכת משתמשים בפונקציונליות שלו. במקרה זה, נוכל להשתמש בדפוס ה-proxy כדי להציג מחלקת פרוקסי ביניים שתקבל בקשות לייצוא דוחות, לרשום את שעת ההתחלה ולהפעיל שרשור נפרד. ברגע שהדוח נוצר, השרשור מסתיים וכולם מרוצים.

דוגמה 2

צוות פיתוח יוצר אתר אירועים. כדי לקבל נתונים על אירועים חדשים, הצוות שואל שירות של צד שלישי. ספרייה פרטית מיוחדת מקלה על האינטראקציה עם השירות. במהלך הפיתוח מתגלה בעיה: מערכת הצד השלישי מעדכנת את הנתונים שלה פעם ביום, אך נשלחת אליה בקשה בכל פעם שמשתמש מרענן עמוד. זה יוצר מספר רב של בקשות, והשירות מפסיק להגיב. הפתרון הוא לאחסן את תגובת השירות במטמון ולהחזיר את התוצאה המאוחסנת למבקרים כאשר הדפים נטענים מחדש, תוך עדכון המטמון לפי הצורך. במקרה זה, דפוס עיצוב ה-proxy הוא פתרון מצוין שאינו משנה את הפונקציונליות הקיימת.

העיקרון מאחורי תבנית העיצוב

כדי ליישם דפוס זה, עליך ליצור מחלקת proxy. הוא מיישם את הממשק של מחלקת השירות, מחקה את ההתנהגות שלו עבור קוד הלקוח. באופן זה, הלקוח מקיים אינטראקציה עם פרוקסי במקום האובייקט האמיתי. ככלל, כל הבקשות מועברות למחלקת השירות, אך עם פעולות נוספות לפני או אחרי. במילים פשוטות, פרוקסי הוא שכבה בין קוד הלקוח לאובייקט היעד. שקול את הדוגמה של תוצאות שאילתות במטמון מדיסק קשיח ישן ואיטי מאוד. נניח שאנחנו מדברים על לוח זמנים של רכבות חשמליות באיזו אפליקציה עתיקה שאי אפשר לשנות את ההיגיון שלה. מדי יום מוכנס דיסק עם לוח זמנים מעודכן בשעה קבועה. אז יש לנו:
  1. TrainTimetableמִמְשָׁק.
  2. ElectricTrainTimetable, המיישמת את הממשק הזה.
  3. קוד הלקוח מקיים אינטראקציה עם מערכת הקבצים באמצעות מחלקה זו.
  4. TimetableDisplayכיתת לקוחות. השיטה שלו printTimetable()משתמשת בשיטות של ElectricTrainTimetableהמחלקה.
הדיאגרמה פשוטה: דפוס עיצוב פרוקסי: - 2נכון לעכשיו, בכל קריאה של printTimetable()השיטה, ElectricTrainTimetableהמחלקה ניגשת לדיסק, טוענת את הנתונים ומציגה אותם ללקוח. המערכת פועלת בסדר, אבל היא איטית מאוד. כתוצאה מכך, התקבלה ההחלטה להגביר את ביצועי המערכת על ידי הוספת מנגנון מטמון. ניתן לעשות זאת באמצעות דפוס ה-proxy: דפוס עיצוב פרוקסי: - 3לפיכך, TimetableDisplayהמחלקה אפילו לא שמה לב שהיא מקיימת אינטראקציה עם ElectricTrainTimetableProxyהמחלקה במקום המחלקה הישנה. היישום החדש טוען את לוח הזמנים פעם ביום. עבור בקשות חוזרות, הוא מחזיר את האובייקט שנטען קודם לכן מהזיכרון.

אילו משימות הכי טובות עבור פרוקסי?

הנה כמה מצבים שבהם הדפוס הזה בהחלט יהיה שימושי:
  1. שמירה במטמון
  2. אתחול מושהה, או עצל, למה לטעון אובייקט מיד אם אתה יכול לטעון אותו לפי הצורך?
  3. רישום בקשות
  4. אימות ביניים של נתונים וגישה
  5. פתיחת שרשורי עובדים
  6. הקלטת גישה לאובייקט
ויש גם מקרי שימוש אחרים. הבנת העיקרון מאחורי דפוס זה, אתה יכול לזהות מצבים שבהם ניתן ליישם אותו בהצלחה. במבט ראשון, פרוקסי עושה את אותו הדבר כמו חזית , אבל זה לא המקרה. ל- proxy יש ממשק זהה לאובייקט השירות. כמו כן, אל תבלבל את הדפוס הזה עם דפוסי הדקורטור או המתאם . דקורטור מספק ממשק מורחב, ומתאם מספק ממשק חלופי.

יתרונות וחסרונות

  • + אתה יכול לשלוט בגישה לאובייקט השירות איך שתרצה
  • + יכולות נוספות הקשורות לניהול מחזור החיים של אובייקט השירות
  • + זה עובד ללא אובייקט שירות
  • + זה משפר ביצועים ואבטחת קוד.
  • - קיים סיכון שהביצועים עלולים להחמיר עקב בקשות נוספות
  • - זה הופך את ההיררכיה המעמדית למסובכת יותר

דפוס הפרוקסי בפועל

בואו ליישם מערכת שקוראת לוחות זמנים של רכבות מדיסק קשיח:
public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
הנה המחלקה המיישמת את הממשק הראשי:
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 "";
   }
}
בכל פעם שאתה מקבל את לוח הזמנים של הרכבת, התוכנית קוראת קובץ מהדיסק. אבל זו רק ההתחלה של הצרות שלנו. הקובץ כולו נקרא בכל פעם שאתה מקבל את לוח הזמנים אפילו לרכבת בודדת! טוב שקוד כזה קיים רק בדוגמאות של מה לא לעשות :) Class 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]);
       }
   }
}
קובץ לדוגמה:

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
בואו נבדוק את זה:
public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
תְפוּקָה:

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
כעת נעבור על השלבים הנדרשים כדי להציג את הדפוס שלנו:
  1. הגדירו ממשק המאפשר שימוש בפרוקסי במקום האובייקט המקורי. בדוגמה שלנו, זה TrainTimetable.

  2. צור את מחלקת ה-proxy. צריך להיות לו הפניה לאובייקט השירות (צור אותו במחלקה או העבר לבנאי).

    הנה כיתת ה-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;
       }
    }

    בשלב זה, אנו פשוט יוצרים מחלקה עם הפניה לאובייקט המקורי ומעבירים אליו את כל הקריאות.

  3. בואו ליישם את ההיגיון של מחלקת ה-proxy. בעיקרון, שיחות מופנות תמיד לאובייקט המקורי.

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

    הבדיקה getTimetable()בודקת האם מערך לוח הזמנים נשמר בזיכרון. אם לא, הוא שולח בקשה לטעינת הנתונים מהדיסק ושומר את התוצאה. אם לוח הזמנים כבר התבקש, הוא מחזיר במהירות את האובייקט מהזיכרון.

    הודות לפונקציונליות הפשוטה שלה, לא היה צורך להפנות את שיטת getTrainDepartureTime() לאובייקט המקורי. פשוט שכפלנו את הפונקציונליות שלו בשיטה חדשה.

    אל תעשה את זה. אם אתה צריך לשכפל את הקוד או לעשות משהו דומה, משהו השתבש, ואתה צריך להסתכל על הבעיה שוב מזווית אחרת. בדוגמה הפשוטה שלנו, לא הייתה לנו אפשרות אחרת. אבל בפרויקטים אמיתיים, סביר להניח שהקוד ייכתב בצורה נכונה יותר.

  4. בקוד הלקוח, צור אובייקט proxy במקום האובייקט המקורי:

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

    חשבון

    
    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

    מעולה, זה עובד כמו שצריך.

    אתה יכול גם לשקול את האפשרות של מפעל שיוצר גם אובייקט מקורי וגם אובייקט פרוקסי, בהתאם לתנאים מסוימים.

לפני שנפרדים, הנה קישור מועיל

זה הכל להיום! זה לא יהיה רעיון רע לחזור לשיעורים ולנסות את הידע החדש שלך בפועל :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION