JavaRush /בלוג Java /Random-HE /אילו בעיות פותרת דפוס העיצוב של המתאם?

אילו בעיות פותרת דפוס העיצוב של המתאם?

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

פרטים נוספים על הבעיה

ראשית, בואו נדמה את ההתנהגות של המערכת הישנה. נניח שזה מייצר סיבות לאיחור לעבודה או ללימודים. לשם כך, יש לנו ממשק Excuseשמכיל את המתודות generateExcuse(), likeExcuse()ו- dislikeExcuse().
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
ממשק זה מיושם על ידי המחלקה WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};

   @Override
   public String generateExcuse() { // Случайно выбираем отговорку из массива
       String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
               sorryOptions[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Дублируем элемент в массиве, чтобы шанс его выпадения был выше
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Удаляем элемент из массива
   }
}
בואו נבדוק את הדוגמה:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
סיכום:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
עכשיו בואו נדמיין שהשקת את השירות, אספת סטטיסטיקות ושמת לב שרוב משתמשי השירות הם סטודנטים באוניברסיטה. כדי לשפר אותה לצרכי הקבוצה הזו, הזמנת במיוחד עבורה מערכת יצירת תירוצים ממפתח אחר. צוות הפיתוח ערך מחקר, ריכז דירוגים, חיבר בינה מלאכותית, הוסיף אינטגרציה עם פקקים, מזג אוויר וכדומה. עכשיו יש לך ספרייה ליצירת תירוצים לסטודנטים, אבל הממשק לאינטראקציה איתה שונה - StudentExcuse:
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
לממשק יש שתי שיטות: generateExcuse, שיוצר תירוץ, ו- dislikeExcuse, שחוסמת את התירוץ כך שלא יופיע בעתיד. ספריית צד שלישי סגורה לעריכה - לא ניתן לשנות את קוד המקור שלה. כתוצאה מכך, במערכת שלך יש שתי מחלקות שמיישמות את הממשק Excuse, וספרייה עם מחלקה SuperStudentExcuseשמטמעת את הממשק StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
לא ניתן לשנות את הקוד. הסכימה הנוכחית תיראה כך: אילו בעיות מתאם - 2 דפוס עיצוב פותר?גרסה זו של המערכת פועלת רק עם ממשק ה-Excuse. לא ניתן לשכתב את הקוד: באפליקציה גדולה, שינויים כאלה יכולים לקחת זמן רב או לשבור את הלוגיקה של האפליקציה. אתה יכול להציע להציג את הממשק הראשי ולהגדיל את ההיררכיה: אילו בעיות פותרת דפוס העיצוב של המתאם - 3לשם כך, עליך לשנות את שם הממשק Excuse. אבל ההיררכיה הנוספת אינה רצויה ביישומים רציניים: הכנסת אלמנט שורש משותף שוברת את הארכיטקטורה. עליך ליישם מחלקת ביניים שתאפשר לך להשתמש בפונקציונליות חדשה וישנה עם אובדן מינימלי. בקיצור, אתה צריך מתאם .

כיצד פועלת דפוס המתאם

מתאם הוא אובייקט ביניים שהופך קריאות לשיטות באובייקט אחד למובנות לאחר. בואו ליישם מתאם עבור הדוגמה שלנו ונקרא לזה Middleware. המתאם שלנו חייב ליישם ממשק התואם לאחד מהאובייקטים. תן לזה להיות Excuse. הודות לכך, Middlewareהוא יכול לקרוא לשיטות של האובייקט הראשון. Middlewareמקבל שיחות ומעביר אותן לאובייקט השני בפורמט תואם. כך נראה היישום של שיטה Middlewareעם שיטות : generateExcusedislikeExcuse
public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
בדיקה (בקוד לקוח):
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
סיכום:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
תירוץ מדהים, מותאם לתנאי מזג האוויר הנוכחיים, פקקים או שיבושים בלוח הזמנים של התחבורה הציבורית. השיטה generateExcuseפשוט מעבירה את הקריאה לאובייקט אחר, ללא טרנספורמציות נוספות. השיטה dislikeExcuseדרשה תחילה הצבת התירוץ ברשימה שחורה של מסד נתונים. עיבוד נתונים ביניים נוסף הוא הסיבה לכך שדפוס המתאם אהוב. אבל מה לגבי שיטה likeExcuseשנמצאת בממשק Excuse, אבל לא ב StudentExcuse? פעולה זו אינה נתמכת בפונקציונליות החדשה. במקרה זה, הם מצאו חריג UnsupportedOperationException: הוא נזרק אם הפעולה המבוקשת אינה נתמכת. בואו נשתמש בזה. כך נראה יישום הכיתה החדש Middleware:
public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("Метод likeExcuse не поддерживается в новом функционале");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Метод обращается за дополнительной информацией к БД,
       // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
   }
}
במבט ראשון, פתרון זה לא נראה מוצלח, אך הדמיית הפונקציונליות יכולה להוביל למצב מורכב יותר. אם הלקוח קשוב והמתאם מתועד היטב, פתרון זה מקובל.

מתי להשתמש במתאם

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

  2. כאשר למספר תת-מחלקות קיימות חייבות להיות פונקציונליות משותפת. במקום תת מחלקות נוספות (יצירתן תוביל לשכפול קוד), עדיף להשתמש במתאם.

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

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

לא להתבלבל עם חזית ודקורטור

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

אלגוריתם יישום שלב אחר שלב

  1. ראשית, ודא שיש בעיה שדפוס זה יכול לפתור.

  2. הגדירו ממשק לקוח שעבורו ישמש מחלקה אחרת.

  3. הטמע מחלקת מתאם המבוססת על הממשק שהוגדר בשלב הקודם.

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

  5. הטמע את כל שיטות ממשק הלקוח במתאם. השיטה יכולה:

    • העבר את השיחה ללא שינוי;

    • שינוי נתונים, הגדלת/הקטנת מספר הקריאות לשיטת היעד, הרחב עוד יותר את הרכב הנתונים וכו'.

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

  6. אם האפליקציה משתמשת במתאם רק דרך ממשק הלקוח (כמו בדוגמה למעלה), זה יאפשר להרחיב מתאמים ללא כאב בעתיד.

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