JavaRush /בלוג Java /Random-HE /דפוס עיצוב פרוקסי

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

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

למה אתה צריך סגן?

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

דוגמה 1

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

דוגמה 2

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

איך התבנית עובדת

כדי ליישם דפוס זה, עליך ליצור מחלקת proxy. הוא מיישם ממשק מחלקת שירות, המדמה את ההתנהגות שלו עבור קוד הלקוח. לפיכך, במקום האובייקט האמיתי, הלקוח מקיים אינטראקציה עם ה-proxy שלו. בדרך כלל, כל הבקשות מועברות למחלקת השירות, אך עם פעולות נוספות לפני או אחרי הקריאה שלה. במילים פשוטות, אובייקט פרוקסי זה הוא שכבה בין קוד הלקוח לאובייקט היעד. בואו נסתכל על דוגמה של שמירה במטמון של בקשה מדיסק ישן איטי מאוד. תן לזה להיות לוח זמנים של רכבת חשמלית באיזה יישום עתיק, שלא ניתן לשנות את עקרון הפעולה שלו. הדיסק עם לוח הזמנים המעודכן מוכנס כל יום בשעה קבועה. אז יש לנו:
  1. ממשק TimetableTrains.
  2. המחלקה TimetableElectricTrainsהמיישמת את הממשק הזה.
  3. דרך המחלקה הזו קוד הלקוח מקיים אינטראקציה עם מערכת הקבצים בדיסק.
  4. כיתת לקוחות DisplayTimetable. השיטה שלה printTimetable()משתמשת בשיטות של TimetableElectricTrains.
הסכימה פשוטה: דפוס עיצוב פרוקסי - 2נכון לעכשיו, בכל פעם שמתודה נקראת, printTimetable()המחלקה TimetableElectricTrainsניגשת לדיסק, פורקת נתונים ומספקת אותם ללקוח. מערכת זו פועלת היטב, אך איטית מאוד. לכן, הוחלט להגביר את ביצועי המערכת על ידי הוספת מנגנון מטמון. ניתן לעשות זאת באמצעות דפוס ה-Proxy: דפוס עיצוב פרוקסי - 3כך הכיתה DisplayTimetableאפילו לא תשים לב שהיא מקיימת אינטראקציה עם הכיתה TimetableElectricTrainsProxyולא עם הכיתה הקודמת. המימוש החדש טוען את לוח הזמנים פעם ביום, ובבקשות חוזרות, מחזיר את האובייקט שכבר נטען מהזיכרון.

לאילו משימות עדיף להשתמש ב-Proxy?

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

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

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

תחליף דפוס בפועל

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

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
בואו נבחן:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
סיכום:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
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
כעת נעבור על השלבים של יישום הדפוס שלנו:
  1. הגדר ממשק המאפשר לך להשתמש בפרוקסי חדש במקום האובייקט המקורי. בדוגמה שלנו זה TimetableTrains.

  2. צור מחלקה פרוקסי. הוא חייב להכיל הפניה לאובייקט שירות (ליצור במחלקה או לעבור בבנאי);

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

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

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

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

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

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

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

  4. החלף את יצירת האובייקט המקורי בקוד הלקוח באובייקט חלופי:

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

    בְּדִיקָה

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    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

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

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

קישור שימושי במקום נקודה

  1. מאמר מצוין על דפוסים וקצת על "סגן"

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