JavaRush /בלוג Java /Random-HE /ממשקים ב-Java

ממשקים ב-Java

פורסם בקבוצה
שלום! היום נדבר על מושג חשוב בג'אווה - ממשקים. המילה בוודאי מוכרת לך. לדוגמה, לרוב תוכניות המחשב והמשחקים יש ממשקים. במובן הרחב, ממשק הוא סוג של "שליטה מרחוק" המחבר בין שני צדדים המקיימים אינטראקציה זה עם זה. דוגמה פשוטה לממשק מחיי היומיום היא שלט רחוק לטלוויזיה. הוא מחבר בין שני חפצים, אדם וטלוויזיה, ומבצע משימות שונות: להגביר או להנמיך את הווליום, להחליף ערוצים, להדליק או לכבות את הטלוויזיה. צד אחד (האדם) צריך לגשת לממשק (לחץ על כפתור השלט הרחוק) כדי שהצד השני יבצע את הפעולה. לדוגמה, שהטלוויזיה תעביר את הערוץ לערוץ הבא. במקרה זה, המשתמש אינו צריך לדעת את המכשיר של הטלוויזיה וכיצד תהליך החלפת הערוץ מיושם בתוכו. מדוע יש צורך בממשקים ב-Java - 1כל מה שיש למשתמש גישה אליו הוא הממשק . המשימה העיקרית היא להשיג את התוצאה הרצויה. מה זה קשור לתכנות וג'אווה? ישיר :) יצירת ממשק דומה מאוד ליצירת מחלקה רגילה, אך במקום המילה classאנו מציינים את המילה interface. בואו נסתכל על ממשק Java הפשוט ביותר ונבין איך הוא עובד ולמה הוא נחוץ:
public interface Swimmable  {

     public void swim();
}
יצרנו ממשק Swimmableשיכול לשחות . זה משהו כמו השלט הרחוק שלנו, שיש לו "כפתור" אחד: השיטה swim() היא "שחות". כיצד נוכל להשתמש ב"שלט רחוק " זה? לצורך כך השיטה, דהיינו. יש ליישם את הכפתור בשלט הרחוק שלנו. כדי להשתמש בממשק, השיטות שלו חייבות להיות מיושמות על ידי כמה מחלקות של התוכנית שלנו. בואו נמציא כיתה שהאובייקטים שלה מתאימים לתיאור "יכול לשחות". לדוגמה, מחלקת הברווזים מתאימה Duck:
public class Duck implements Swimmable {

    public void swim() {
        System.out.println("Duck, swim!");
    }

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
מה אנחנו רואים כאן? מחלקה Duckמשויכת לממשק Swimmableבאמצעות מילת המפתח implements. אם אתה זוכר, השתמשנו במנגנון דומה כדי לחבר שתי מחלקות בירושה, רק שהייתה המילה " מרחיב ". " public class Duck implements Swimmable" ניתן לתרגם מילולית לצורך הבהירות: "מחלקה ציבורית Duckמיישמת את הממשק Swimmable." המשמעות היא שמחלקה הקשורה לממשק חייבת ליישם את כל השיטות שלה. שימו לב: בכיתה שלנו, Duckבדיוק כמו בממשק , Swimmableיש שיטה swim(), ובתוכה יש איזושהי היגיון. זוהי דרישה מחייבת. אם רק נכתב " public class Duck implements Swimmable" ולא ניצור מתודה swim()במחלקה Duck, המהדר היה נותן לנו שגיאה: Duck אינו מופשט ואינו עוקף את השיטה המופשטת swim() ב-Swimmable מדוע זה קורה? אם נסביר את השגיאה באמצעות הדוגמה של טלוויזיה, מסתבר שאנו נותנים לאדם שלט עם כפתור "שינוי ערוץ" מטלוויזיה שלא יודעת להחליף ערוצים. בשלב זה, לחץ על הכפתור כמה שאתה רוצה, שום דבר לא יעבוד. השלט הרחוק עצמו אינו מחליף ערוצים: הוא רק נותן אות לטלוויזיה, שבתוכה מיושם תהליך מורכב של החלפת ערוץ. כך זה עם הברווז שלנו: עליו להיות מסוגל לשחות כדי שניתן יהיה לגשת אליו באמצעות הממשק Swimmable. אם היא לא תדע לעשות זאת, הממשק Swimmableלא יחבר בין שני הצדדים - האדם והתוכנית. אדם לא יוכל להשתמש בשיטה swim()כדי לגרום לאובייקט Duckבתוך תוכנית לצוף. עכשיו ראית בצורה ברורה יותר למה מיועדים ממשקים. ממשק מתאר את ההתנהגות שחייבות להיות מחלקות שמיישמות ממשק זה. "התנהגות" היא אוסף של שיטות. אם ברצוננו ליצור מספר שליחים, הדרך הקלה ביותר לעשות זאת היא על ידי יצירת ממשק Messenger. מה כל שליח אמור להיות מסוגל לעשות? בצורה פשוטה, קבלו ושלחו הודעות.
public interface Messenger{

     public void sendMessage();

     public void getMessage();
}
ועכשיו אנחנו יכולים פשוט ליצור את שיעורי המסנג'ר שלנו על ידי יישום הממשק הזה. המהדר עצמו "יאלץ" אותנו ליישם אותם בתוך מחלקות. מִברָק:
public class Telegram implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a message to Telegram!");
    }

     public void getMessage() {
         System.out.println("Reading the message in Telegram!");
     }
}
WhatsApp:
public class WhatsApp implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a WhatsApp message!");
    }

     public void getMessage() {
         System.out.println("Reading a WhatsApp message!");
     }
}
Viber:
public class Viber implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a message to Viber!");
    }

     public void getMessage() {
         System.out.println("Reading a message in Viber!");
     }
}
אילו יתרונות זה מספק? החשוב שבהם הוא צימוד רופף. תארו לעצמכם שאנחנו מעצבים תוכנית שבה נאסוף נתוני לקוחות. למחלקה Clientחייב להיות שדה המציין באיזה מסנג'ר הלקוח משתמש. בלי ממשקים זה היה נראה מוזר:
public class Client {

    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
יצרנו שלושה שדות, אבל ללקוח יכול בקלות להיות רק שליח אחד. אנחנו פשוט לא יודעים איזה. וכדי לא להישאר ללא תקשורת עם הלקוח, אתה צריך "לדחוף" את כל האפשרויות האפשריות לתוך הכיתה. מסתבר שאחד או שניים מהם תמיד יהיו שם null, ואין צורך בהם כלל כדי שהתוכנית תעבוד. במקום זאת, עדיף להשתמש בממשק שלנו:
public class Client {

    private Messenger messenger;
}
זוהי דוגמה ל"צימוד רופף"! במקום לציין מחלקת מסנג'ר ספציפית במחלקה Client, אנו פשוט מזכירים שללקוח יש מסנג'ר. איזה מהם ייקבע במהלך התוכנית. אבל למה אנחנו צריכים ממשקים בשביל זה? למה הם נוספו לשפה בכלל? השאלה טובה ונכונה! ניתן להגיע לאותה תוצאה באמצעות ירושה רגילה, נכון? הכיתה Messengerהיא כיתת ההורים, ו Viber, Telegramוהם WhatsAppהיורשים. אכן, אפשר לעשות זאת. אבל יש מלכוד אחד. כפי שאתה כבר יודע, אין ירושה מרובה ב-Java. אבל יש מימושים מרובים של ממשקים. מחלקה יכולה ליישם כמה ממשקים שהיא רוצה. תארו לעצמכם שיש לנו מחלקה Smartphoneשיש בה שדה Application- אפליקציה המותקנת בסמארטפון.
public class Smartphone {

    private Application application;
}
האפליקציה והמסנג'ר דומים, כמובן, אבל עדיין מדובר בדברים שונים. Messenger יכול להיות גם נייד וגם שולחני, בעוד Application היא אפליקציה לנייד. לכן, אם השתמשנו בירושה, לא נוכל להוסיף אובייקט Telegramלמחלקה Smartphone. אחרי הכל, כיתה Telegramלא יכולה לרשת מ- Applicationומ Messenger! וכבר הצלחנו לרשת אותו מ Messenger, ולהוסיף אותו לכיתה בצורה זו Client. אבל כיתה Telegramיכולה ליישם את שני הממשקים בקלות! לכן, במחלקה Clientנוכל ליישם אובייקט Telegramכ- Messengerובמחלקה Smartphoneכ- Application. הנה איך זה נעשה:
public class Telegram implements Application, Messenger {

    //...methods
}

public class Client {

    private Messenger messenger;

    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {

    private Application application;

    public Smartphone() {
        this.application = new Telegram();
    }
}
כעת נוכל להשתמש בכיתה Telegramכרצוננו. איפשהו הוא יפעל בתפקיד של Application, איפשהו בתפקיד של Messenger. בטח כבר שמתם לב ששיטות בממשקים תמיד "ריקות", כלומר אין להן יישום. הסיבה לכך פשוטה: ממשק מתאר התנהגות, לא מיישם אותה. "כל האובייקטים של מחלקות שמיישמות את הממשק Swimmableחייבים להיות מסוגלים לצוף": זה כל מה שהממשק אומר לנו. איך בדיוק ישחו דג, ברווז או סוס יש שאלה לשיעורים Fish, Duckולא Horseלממשק. בדיוק כמו שהחלפת ערוץ היא המשימה של טלוויזיה. השלט פשוט נותן לך כפתור לעשות את זה. עם זאת, ל-Java8 יש תוספת מעניינת - שיטות ברירת מחדל. לדוגמה, לממשק שלך יש 10 שיטות. 9 מהם מיושמים בצורה שונה במחלקות שונות, אבל אחת מיושמת זהה בכולם. בעבר, לפני שחרורו של Java8, לשיטות בתוך ממשקים לא היה יישום כלל: המהדר השליך מיד שגיאה. עכשיו אתה יכול לעשות את זה ככה:
public interface Swimmable {

   public default void swim() {
       System.out.println("Swim!");
   }

   public void eat();

   public void run();
}
באמצעות מילת המפתח default, יצרנו שיטה בממשק עם מימוש ברירת מחדל. נצטרך ליישם את שתי השיטות האחרות, eat()ואת run()עצמנו בכל המחלקות שיטמיעו את Swimmable. אין צורך לעשות זאת בשיטה swim(): היישום יהיה זהה בכל המחלקות. אגב, נתקלת בממשקים יותר מפעם אחת במשימות עבר, למרות שלא שמת לב לזה בעצמך :) הנה דוגמה ברורה: למה אנחנו צריכים ממשקים ב-Java - 2עבדת עם ממשקים Listו- Set! ליתר דיוק, עם היישום שלהם - ArrayList, LinkedList, HashSetואחרים. אותה תרשים מציג דוגמה כאשר מחלקה אחת מיישמת מספר ממשקים בבת אחת. לדוגמה, LinkedListהוא מיישם את הממשקים Listו Deque(תור דו צדדי). אתה גם מכיר את הממשק Map, או ליתר דיוק, עם ההטמעות שלו - HashMap. אגב, בתרשים הזה אפשר לראות תכונה אחת: ממשקים יכולים לעבור בירושה אחד מהשני. הממשק SortedMapעובר בירושה מ- Map, ועובר Dequeבירושה מהתור Queue. זה הכרחי אם אתה רוצה להראות את הקשר בין ממשקים, אבל ממשק אחד הוא גרסה מורחבת של אחר. בואו נסתכל על דוגמה עם ממשק Queue- תור. עוד לא עברנו על הקולקציות Queue, אבל הן די פשוטות ומסודרות כמו תור רגיל בחנות. אתה יכול להוסיף אלמנטים רק לסוף התור, ולקחת אותם רק מההתחלה. בשלב מסוים, המפתחים היו צריכים גרסה מורחבת של התור כדי שניתן יהיה להוסיף ולקבל אלמנטים משני הצדדים. כך נוצר ממשק Deque- תור דו כיווני. הוא מכיל את כל השיטות של תור רגיל, מכיוון שהוא ה"אב" של תור דו-כיווני, אך נוספו שיטות חדשות.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION