CodeGym /בלוג Java /Random-HE /למה אנחנו צריכים ממשקים ב-Java
John Squirrels
רָמָה
San Francisco

למה אנחנו צריכים ממשקים ב-Java

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

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

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

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
"מה אנחנו רואים כאן? המחלקה Duck 'משויכת' לממשק CanSwim על ידי מילת המפתח implements . אתה אולי זוכר שהשתמשנו במנגנון דומה כדי לשייך שתי מחלקות דרך ירושה, אבל במקרה כזה השתמשנו במילה extends. עבור בהירות מלאה, אנו יכולים לתרגם את ' מחלקה ציבורית Duck מיישמת CanSwim ' מילולית כ: ' מחלקת Duck הציבורית מיישמת את ממשק CanSwim '. זה אומר שמחלקה הקשורה לממשק חייבת ליישם את כל השיטות שלה. הערה: Duckהמחלקה שלנו, בדיוק כמו הממשק CanSwim, יש swim()שיטה, והוא מכיל קצת היגיון. זו דרישת חובה. אם רק נכתוב public class Duck implements CanSwimבלי ליצור swim()מתודה במחלקה Duck, המהדר יתן לנו שגיאה: Duck לא מופשט ולא עוקף שחייה של מתודה מופשטת () ב-CanSwim למה? למה זה קורה? אם נסביר את השגיאה באמצעות דוגמה לטלוויזיה, זה יהיה כמו להעביר למישהו שלט רחוק לטלוויזיה עם כפתור 'שנה ערוץ' שאינו יכול לשנות ערוצים. אתה יכול ללחוץ על הכפתור כמה שאתה רוצה, אבל זה לא יעבוד. השלט הרחוק לא מחליף ערוצים בעצמו: הוא רק שולח אות לטלוויזיה, שמיישמת את התהליך המורכב של החלפת ערוצים. וכך גם לגבי הברווז שלנו: עליו לדעת לשחות כדי שניתן יהיה לקרוא לו באמצעות CanSwimהממשק. אם הוא לא יודע איך, CanSwimהממשק לא מחבר בין שני הצדדים - האדם והתוכנית. האדם לא יוכל להשתמש בשיטה 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 Telegram message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Telegram message!");
     }
}
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 Viber message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Viber message!");
     }
}
אילו יתרונות זה מספק? החשוב שבהם הוא צימוד רופף. תארו לעצמכם שאנחנו מעצבים תוכנית שתאסוף נתוני לקוחות. המחלקה 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. אכן, זה אפשרי. אבל יש תקלה אחת. כפי שאתה כבר יודע, לג'אווה אין ירושה מרובה. אבל יש תמיכה במספר ממשקים. מחלקה יכולה ליישם כמה ממשקים שתרצה. תארו לעצמכם שיש לנו Smartphoneמחלקה שיש בה Appשדה אחד, שמייצג אפליקציה המותקנת בסמארטפון.
public class Smartphone {

    private App app;
}
כמובן, אפליקציה ומסנג'ר דומים, אבל הם עדיין דברים שונים. יכולות להיות גרסאות לנייד ולשולחן העבודה של מסנג'ר, אבל האפליקציה מייצגת ספציפית אפליקציה לנייד. הנה העסקה - אם היינו משתמשים בירושה, לא היינו יכולים להוסיף אובייקט Telegramלמחלקה Smartphone. אחרי הכל, Telegramהמחלקה לא יכולה לרשת בו זמנית Appו Messenger! וכבר העברנו אותו בירושה Messengerוהוספנו אותו לכיתה Client. אבל Telegramהכיתה יכולה ליישם את שני הממשקים בקלות! בהתאם לכך, נוכל לתת Clientלמחלקה Telegramאובייקט בתור Messenger, ונוכל לתת אותו למחלקה Smartphoneבתור App. הנה איך אתה עושה את זה:
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בכיתה איך שאנחנו רוצים. במקומות מסוימים הוא פועל כ- App. במקומות אחרים הוא פועל כ- Messenger. ודאי כבר שמתם לב ששיטות ממשק הן תמיד 'ריקות', כלומר אין להן יישום. הסיבה לכך פשוטה: הממשק מתאר התנהגות, אך אינו מיישם אותה. 'כל האובייקטים שמיישמים את CanSwimהממשק חייבים להיות מסוגלים לשחות': זה כל מה שהממשק אומר לנו. הדרך הספציפית שבה דגים, ברווזים וסוסים שוחים היא שאלה עבור ה- Fish, Duck, ושיעורים Horse, לא לממשק. בדיוק כמו שהחלפת ערוץ היא משימה עבור הטלוויזיה. השלט פשוט נותן לך כפתור בשביל זה. עם זאת, תוספת מעניינת הופיעה ב-Java 8 - שיטות ברירת מחדל. לדוגמה, לממשק שלך יש 10 שיטות. ל-9 מהם יש יישומים שונים במחלקות שונות, אבל אחד מיושם זהה לכולם. בעבר, לפני Java 8, לשיטות ממשק לא היה מימוש כלשהו: המהדר מיד נתן שגיאה. עכשיו אתה יכול לעשות משהו כזה:
public interface CanSwim {

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

   public void eat();

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