שיטת עומס יתר ועקיפות ב- Java

1. סקירה כללית

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

במאמר זה נלמד את יסודות המושגים הללו ונראה באילו מצבים הם יכולים להיות שימושיים.

2. עומס יתר בשיטה

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

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

אם נתנו לשיטות שמות מטעים או דו משמעיים, כגון להכפיל 2 (), להכפיל 3 (), להכפיל 4 (), אז זה יהיה ממשק API מעוצב בצורה גרועה. כאן נכנסת לעומס יתר של השיטה.

במילים פשוטות, אנו יכולים ליישם עומס יתר בשיטות בשתי דרכים שונות:

  • יישום שניים או יותר שיטות בעלות שם זהה אך לוקחות מספר שונה של טיעונים
  • יישום שניים או יותר שיטות בעלות אותו שם אך לוקחות טיעונים מסוגים שונים

2.1. מספר ויכוחים שונים

ה מַכפִּיל בכיתה מראה, בקצרה, איך להעמיס את ה- לְהַכפִּיל() שיטה פשוט על ידי הגדרת שתי יישומים שלוקחים מספר שונה של טיעונים:

מחלקה ציבורית מכפיל {public int מכפיל (int a, int b) {להחזיר a * b; } כפול ציבורי int (int a, int b, int c) {return a * b * c; }}

2.2. טיעונים מסוגים שונים

באופן דומה, אנו יכולים להעמיס על ה- לְהַכפִּיל() שיטה על ידי כך שהוא מקבל טיעונים מסוגים שונים:

מחלקה ציבורית מכפיל {public int מכפיל (int a, int b) {להחזיר a * b; } הכפל כפול ציבורי (כפול a, כפול b) {החזר a * b; }} 

יתר על כן, זה לגיטימי להגדיר את מַכפִּיל כיתה עם שני סוגי העמסת יתר של השיטות:

כיתה ציבורית מכפיל {ציבורי אינט כפול (int a, int b) {החזר a * b; } כפול ציבורי int (int a, int b, int c) {return a * b * c; } הכפל כפול ציבורי (כפול a, כפול b) {החזר a * b; }} 

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

כדי להבין מדוע - בואו ניקח בחשבון את הדוגמה הבאה:

public int להכפיל (int a, int b) {להחזיר a * b; } הכפל כפול ציבורי (int a, int b) {החזר a * b; }

במקרה הזה, הקוד פשוט לא היה קומפילציה בגלל עמימות שיחת השיטה המהדר לא היה יודע איזה יישום של לְהַכפִּיל() להתקשר.

2.3. קידום סוג

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

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

כדי להבין בצורה ברורה יותר כיצד קידום סוגים עובד, שקול את היישומים הבאים של ה- לְהַכפִּיל() שיטה:

הכפל כפול ציבורי (int a, long b) {return a * b; } כפול ציבורי int (int a, int b, int c) {return a * b * c; } 

עכשיו, קוראים לשיטה בשניים int הטיעונים יביאו לטיעון השני ארוך, כמו במקרה זה אין יישום תואם של השיטה לשניים int טיעונים.

בואו נראה מבחן יחידה מהיר כדי להדגים קידום סוגים:

@Test הציבור בטל כאשר CallMultiplyAndNoMatching_thenTypePromotion () {assertThat (multiplier.multiply (10, 10)). IsEqualTo (100.0); }

לעומת זאת, אם אנו קוראים לשיטה עם יישום תואם, קידום סוג פשוט לא מתרחש:

@Test הציבור בטל כאשר CallMultiplyAndMatching_thenNoTypePromotion () {assertThat (multiplier.multiply (10, 10, 10)). IsEqualTo (1000); }

הנה סיכום של כללי קידום הסוג החלים על עומס יתר בשיטות:

  • בתים ניתן לקדם ל קצר, אינט, ארוך, צף, אוֹ לְהַכפִּיל
  • קצר ניתן לקדם ל int, ארוך, לצוף, אוֹ לְהַכפִּיל
  • לְהַשְׁחִיר ניתן לקדם ל int, ארוך, לצוף, אוֹ לְהַכפִּיל
  • int ניתן לקדם ל ארוך, צף, אוֹ לְהַכפִּיל
  • ארוך ניתן לקדם ל לָצוּף אוֹ לְהַכפִּיל
  • לָצוּף ניתן לקדם ל לְהַכפִּיל

2.4. כריכה סטטית

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

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

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

3. עקיפת שיטה

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

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

בואו נראה כעת כיצד להשתמש בשיטת עקיפת שיטה על ידי יצירת מערכת יחסים פשוטה, מבוססת ירושה ("is-a").

הנה מחלקת הבסיס:

מחלקה ציבורית רכב {public String accelerate (long mph) {return "הרכב מאיץ ב:" + mph + "MPH."; } stop String מחרוזי () {return "הרכב עצר."; } ריצת מחרוזת ציבורית () {return "הרכב פועל."; }}

והנה תת-מחלקה מתוכננת:

רכב בכיתה ציבורית מאריך את הרכב {@Override public מחרוזת להאיץ (ארוך קמ"ש) {חזרה "המכונית מאיצה ב:" + קמ"ש + "MPH."; }}

בהיררכיה שלמעלה פשוט ביטלנו את ה- להאיץ() שיטה על מנת לספק יישום מעודן יותר עבור תת-הסוג אוטו.

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

בואו נכתוב כמה בדיקות יחידה כדי לבדוק את רכב ו אוטו שיעורים:

@Test הציבור בטל כאשר CallAccelerate_thenOneAssertion () {assertThat (vehicle.accelerate (100)) .isEqualTo ("הרכב מאיץ ב: 100 MPH."); } @Test הציבור בטל כאשר CallRun_thenOneAssertion () {assertThat (vehicle.run ()) .isEqualTo ("הרכב פועל."); } @Test הציבור בטל כאשר CallStop_thenOneAssertion () {assertThat (vehicle.stop ()) .isEqualTo ("הרכב עצר."); } @ מבחן ציבורי בטל כאשר CallAccelerate_thenOneAssertion () {assertThat (car.accelerate (80)) .isEqualTo ("המכונית מאיצה ב: 80 MPH."); } @Test הציבור בטל כאשר CallRun_thenOneAssertion () {assertThat (car.run ()) .isEqualTo ("הרכב פועל."); } @Test הציבור בטל כאשר CallStop_thenOneAssertion () {assertThat (car.stop ()) .isEqualTo ("הרכב עצר."); } 

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

@Test הציבור בטל givenVehicleCarInstances_whenCalledRun_thenEqual () {assertThat (vehicle.run ()). IsEqualTo (car.run ()); } @Test public void givenVehicleCarInstances_whenCalledStop_thenEqual () {assertThat (vehicle.stop ()). IsEqualTo (car.stop ()); }

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

לכן, הבדיקה הבאה מוכיחה כי השיטה הנדרשת מופעלת עבור מופע של אוטו:

@ מבחן פומבי בטל כאשר CalledAccelerateWithSameArgument_thenNotEqual () {assertThat (vehicle.accelerate (100)) .isNotEqualTo (car.accelerate (100)); }

3.1. הקלד תחליף

עקרון ליבה ב- OOP הוא החלפת סוג, אשר קשורה קשר הדוק עם עקרון ההחלפה של ליסקוב (LSP).

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

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

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

  • אם שיטה בכיתת הבסיס לוקחת ארגומנטים / ים מסוג מסוים, השיטה הנדרשת צריכה לקחת את אותו סוג או סוג-על (א.ק.א. סותר טיעוני שיטה)
  • אם שיטה במחלקת הבסיס חוזרת בָּטֵל, השיטה הנדרשת צריכה לחזור בָּטֵל
  • אם שיטה במחלקת הבסיס מחזירה פרימיטיבי, השיטה הנדרשת צריכה להחזיר את אותה פרימיטיב
  • אם שיטה במחלקת הבסיס מחזירה סוג מסוים, השיטה הנדרשת צריכה להחזיר את אותו סוג או תת-סוג (ע.ק.א. משתנה סוג החזרה)
  • אם שיטה במחלקת הבסיס מביאה חריג, השיטה הנדרשת חייבת לזרוק את אותו חריג או תת-סוג של חריג המעמד הבסיסי

3.2. כריכה דינמית

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

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

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

4. מסקנה

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

כרגיל, כל דגימות הקוד המוצגות במאמר זה זמינות ב- GitHub.