פולימורפיזם וירושה בג'אווה

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

קבל את הקוד

תוכל לקבל את קוד המקור לאתגר זה ולהריץ בדיקות משלך כאן: //github.com/rafadelnero/javaworld-challengers

ממשקים וירושה בפולימורפיזם

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

 public abstract class JavaMascot { public abstract void executeAction(); } public class Duke extends JavaMascot { @Override public void executeAction() { System.out.println("Punch!"); } } public class Juggy extends JavaMascot { @Override public void executeAction() { System.out.println("Fly!"); } } public class JavaMascotTest { public static void main(String... args) { JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); } } 

הפלט מקוד זה יהיה:

 Punch! Fly! 

בגלל היישומים הספציפיים שלהם, שניהם Dukeלבין Juggyהפעולות של" יבוצע.

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

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

מה המטרה של פולימורפיזם?

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

כדי להבין טוב יותר את מטרת הפולימורפיזם, עיין ב SweetCreator:

 public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List sweetProducer; public SweetCreator(List sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } } 

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

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

סוגי החזרות משתנים במעקף השיטה

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

 public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } } 

מכיוון Dukeשהוא a JavaMascot, אנו מסוגלים לשנות את סוג ההחזרה בעת עקיפה.

פולימורפיזם עם שיעורי הליבה של Java

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

 List list = new ArrayList(); 

כדי להמשיך, שקול דוגמת קוד זו באמצעות ממשק ה- API של Java Collections ללא פולימורפיזם:

 public class ListActionWithoutPolymorphism { // Example without polymorphism void executeVectorActions(Vector vector) {/* Code repetition here*/} void executeArrayListActions(ArrayList arrayList) {/*Code repetition here*/} void executeLinkedListActions(LinkedList linkedList) {/* Code repetition here*/} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) { /* Code repetition here*/} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector()); listAction.executeArrayListActions(new ArrayList()); listAction.executeLinkedListActions(new LinkedList()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList()); } 

קוד מכוער, לא? דמיין שאתה מנסה לשמור עליו! עכשיו תסתכל על אותה דוגמה עם פולימורפיזם:

 public static void main(String … polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List list) { // Execute actions with different lists } } public class ListActionInvoker { public static void main(String... masterPolymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(new Vector()); listAction.executeListActions(new ArrayList()); listAction.executeListActions(new LinkedList()); listAction.executeListActions(new CopyOnWriteArrayList()); } } 

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

הפעלת שיטות ספציפיות בשיחת שיטה פולימורפית

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

 public abstract class MetalGearCharacter { abstract void useWeapon(String weapon); } public class BigBoss extends MetalGearCharacter { @Override void useWeapon(String weapon) { System.out.println("Big Boss is using a " + weapon); } void giveOrderToTheArmy(String orderMessage) { System.out.println(orderMessage); } } public class SolidSnake extends MetalGearCharacter { void useWeapon(String weapon) { System.out.println("Solid Snake is using a " + weapon); } } public class UseSpecificMethod { public static void executeActionWith(MetalGearCharacter metalGearCharacter) { metalGearCharacter.useWeapon("SOCOM"); // The below line wouldn't work // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) { ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); } } public static void main(String... specificPolymorphismInvocation) { executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); } } 

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

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

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

instanceofמילת שמורות

שימו לב למילה השמורה instanceof. לפני שהפעלנו את השיטה הספציפית, שאלנו אם MetalGearCharacterהיא " instanceof" BigBoss. אם זה לא היהBigBoss למשל, היינו מקבלים את המסר החריג הבאים:

 Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

superמילת שמורות

מה אם נרצה להפנות למאפיין או לשיטה ממעמד-על של Java? במקרה זה נוכל להשתמש superבמילה השמורה. לדוגמה:

 public class JavaMascot { void executeAction() { System.out.println("The Java Mascot is about to execute an action!"); } } public class Duke extends JavaMascot { @Override void executeAction() { super.executeAction(); System.out.println("Duke is going to punch!"); } public static void main(String... superReservedWord) { new Duke().executeAction(); } } 

באמצעות מילה שמורה superב- Dukes" executeActionהשיטה מפעילה את השיטה העל. לאחר מכן אנו מבצעים את הפעולה הספציפית מ Duke. לכן אנו יכולים לראות את שתי ההודעות בפלט שלהלן:

 The Java Mascot is about to execute an action! Duke is going to punch! 

קח את אתגר הפולימורפיזם!

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

 public class PolymorphismChallenge { static abstract class Simpson { void talk() { System.out.println("Simpson!"); } protected void prank(String prank) { System.out.println(prank); } } static class Bart extends Simpson { String prank; Bart(String prank) { this.prank = prank; } protected void talk() { System.out.println("Eat my shorts!"); } protected void prank() { super.prank(prank); System.out.println("Knock Homer down"); } } static class Lisa extends Simpson { void talk(String toMe) { System.out.println("I love Sax!"); } } public static void main(String... doYourBest) { new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); } } 

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

בחר את התשובה שלך ותוכל למצוא את התשובה הנכונה למטה.

 A) I love Sax! D'oh Simpson! D'oh B) Sax :) Eat my shorts! I love Sax! D'oh Knock Homer down C) Sax :) D'oh Simpson! Knock Homer down D) I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

מה קרה הרגע? הבנת פולימורפיזם

For the following method invocation:

 new Lisa().talk("Sax :)"); 

the output will be “I love Sax!” This is  because we are passing a String to the method and Lisa has the method.

For the next invocation:

 Simpson simpson = new Bart("D'oh");

simpson.talk();

The output will be "Eat my shorts!" This is because we’re instantiating  the Simpson type with Bart.

Now check this one, which is a little trickier:

 Lisa lisa = new Lisa(); lisa.talk(); 

Here, we are using method overloading with inheritance. We are not passing anything to the talk method, which is why the Simpson talk method is invoked.  In this case the output will be:

 "Simpson!" 

Here’s one more:

 ((Bart) simpson).prank(); 

In this case, the prank String was passed when we instantiated the Bart class with new Bart("D'oh");. In this case,  first the super.prank method will be invoked, followed by the specific prank method from Bart. The output will be:

 "D'oh" "Knock Homer down" 

Video challenge! Debugging Java polymorphism and inheritance

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java polymorphism challenge:

Common mistakes with polymorphism

It’s a common mistake to think it’s possible to invoke a specific method without using casting.

Another mistake is being unsure what method will be invoked when instantiating a class polymorphically. Remember that the method to be invoked is the method of the created instance.

Also remember that method overriding is not method overloading.

It’s impossible to override a method if the parameters are different. It is possible to change the return type of the overridden method if the return type is a subclass of the superclass method.

מה צריך לזכור על פולימורפיזם

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

מקש מענה

התשובה המתמודד Java זה D . התפוקה תהיה:

 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

סיפור זה, "פולימורפיזם וירושה בג'אווה" פורסם במקור על ידי JavaWorld.