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

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

פורסם בקבוצה
פיתוח תוכנה מקשה על ידי רכיבים לא תואמים שצריכים לעבוד יחד. לדוגמה, אם אתה צריך לשלב ספרייה חדשה עם פלטפורמה ישנה שנכתבה בגרסאות קודמות של 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[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
בואו נבדוק את הדוגמה שלנו:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
תְפוּקָה:
"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
כעת דמיינו שהשקת שירות ליצירת תירוצים, אספת נתונים סטטיסטיים ושמת לב שרוב המשתמשים שלך הם סטודנטים באוניברסיטה. כדי לשרת טוב יותר את הקבוצה הזו, ביקשת ממפתח אחר ליצור מערכת שמייצרת תירוצים במיוחד לסטודנטים באוניברסיטה. צוות הפיתוח ערך מחקר שוק, דירג תירוצים, חיבר קצת בינה מלאכותית ושילב את השירות עם דיווחי תנועה, דיווחי מזג אוויר וכו'. עכשיו יש לך ספרייה ליצירת תירוצים לסטודנטים באוניברסיטה, אבל יש לה ממשק אחר: StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
לממשק הזה יש שתי שיטות: generateExcuse, שיוצר תירוץ, ו- dislikeExcuse, המונעת מהתירוץ להופיע שוב בעתיד. לא ניתן לערוך את ספריית הצד השלישי, כלומר לא ניתן לשנות את קוד המקור שלה. מה שיש לנו עכשיו הוא מערכת עם שתי מחלקות שמיישמות את Excuseהממשק, וספרייה עם SuperStudentExcuseמחלקה שמטמעת את StudentExcuseהממשק:

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
לא ניתן לשנות את הקוד. היררכיית המחלקות הנוכחית נראית כך: אילו בעיות פותרת דפוס עיצוב המתאם?  - 2גרסה זו של המערכת פועלת רק עם ממשק ה-Excuse. לא ניתן לשכתב את הקוד: באפליקציה גדולה, ביצוע שינויים כאלה עלול להפוך לתהליך ארוך או לשבור את ההיגיון של האפליקציה. נוכל להציג ממשק בסיס ולהרחיב את ההיררכיה: אילו בעיות פותרת דפוס עיצוב המתאם?  - 3לשם כך, עלינו לשנות את שם Excuseהממשק. אבל ההיררכיה הנוספת אינה רצויה ביישומים רציניים: הכנסת אלמנט שורש משותף שוברת את הארכיטקטורה. עליך ליישם מחלקת ביניים שתאפשר לנו להשתמש גם בפונקציונליות החדשה וגם הישנה עם הפסדים מינימליים. בקיצור, אתה צריך מתאם .

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

מתאם הוא אובייקט ביניים המאפשר לקריאות השיטה של ​​אובייקט אחד להיות מובנות על ידי אחר. בואו ליישם מתאם עבור הדוגמה שלנו ונקרא לזה Middleware. המתאם שלנו חייב ליישם ממשק התואם לאחד מהאובייקטים. תן לזה להיות Excuse. זה מאפשר Middlewareלקרוא לשיטות של האובייקט הראשון. Middlewareמקבל שיחות ומעביר אותן בצורה תואמת לאובייקט השני. להלן היישום Middlewareעם שיטות generateExcuseו dislikeExcuse:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
בדיקה (בקוד לקוח):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
תְפוּקָה:
An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
תירוץ מדהים המותאם לתנאי מזג האוויר הנוכחיים, פקקים או עיכובים בלוחות הזמנים של התחבורה הציבורית. השיטה 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("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
במבט ראשון, הפתרון הזה לא נראה טוב במיוחד, אבל חיקוי הפונקציונליות יכול לסבך את המצב. אם הלקוח שם לב, והמתאם מתועד היטב, פתרון כזה מקובל.

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

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

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

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

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

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

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

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

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

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

  3. הפוך את מחלקת המתאם לרשת את הממשק שהוגדר בשלב הקודם.

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

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

    • העבר שיחות בלי לבצע שינויים

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

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

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

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