JavaRush /בלוג Java /Random-HE /ירושה מרובה ב-Java. השוואה של הרכב וירושה
HonyaSaar
רָמָה
Москва

ירושה מרובה ב-Java. השוואה של הרכב וירושה

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

ירושה מרובה ב-Java

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

"בעיית היהלומים"

כדי להבין את בעיית היהלומים בצורה פשוטה יותר, הבה נניח שהירושה המרובה נתמכת ב-Java. במקרה זה, נוכל לקבל מחלקות עם ההיררכיה המוצגת באיור למטה. היררכיית מעמד היהלומיםנניח שזו SuperClassמחלקה מופשטת שמתארת ​​שיטה מסוימת, והמחלקות ClassAוהן ClassBמחלקות אמיתיות. SuperClass.java
package com.journaldev.inheritance;
public abstract class SuperClass {
   	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
    @Override
 public void doSomething(){
        System.out.println("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
כעת, נניח שהמחלקה ClassCיורשת ClassAובו ClassB-זמנית, ובו-זמנית יש לה את היישום הבא:
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
שימו לב שהמתודה test()קוראת למתודה doSomething()של מחלקת האב, מה שיוביל לאי בהירות מכיוון שהקומפיילר לא יודע לאיזו מתודה על מחלקה צריך לקרוא. בשל צורת דיאגרמת הירושה המעמדית במצב זה, הדומה למתאר של יהלום בעל פנים, הבעיה נקראת "בעיית היהלום". זו הסיבה העיקרית לכך ש-Java אינה תומכת בירושה של מחלקות מרובות. שים לב שבעיה זו עם ירושה של מחלקות מרובות יכולה להתרחש גם עם שלוש מחלקות שיש להן לפחות שיטה אחת משותפת.

ריבוי ירושה וממשקים

אולי שמתם לב שאני תמיד אומר "ירושה מרובה לא נתמכת בין מחלקות", אבל זה נתמך בין ממשקים. דוגמה פשוטה מוצגת להלן: InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

    public void doSomething();
}
InterfaceB.java
package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}
שימו לב שלשני הממשקים יש שיטה עם אותו שם. עכשיו נניח שיש לנו ממשק שיורש משני הממשקים. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
כאן הכל אידיאלי, שכן ממשקים הם רק שמירה/תיאור של שיטה, והטמעת השיטה עצמה תהיה במחלקה הקונקרטית המיישמת את הממשקים הללו, כך שאין אפשרות להיתקל בעמימות עם ירושה מרובה של ממשקים. זו הסיבה ששיעורים ב-Java יכולים לרשת ממשקים מרובים. בואו נראה את זה עם הדוגמה למטה. InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    public static void main(String[] args) {
        InterfaceA objA = new InterfacesImpl();
        InterfaceB objB = new InterfacesImpl();
        InterfaceC objC = new InterfacesImpl();

        //все вызываемые ниже методы получат одинаковую реализацию конкретного класса

        objA.doSomething();
        objB.doSomething();
        objC.doSomething();
    }
}
אולי שמתם לב שבכל פעם שאני עוקף שיטה המתוארת ב- superclass או בממשק, אני משתמש בביאור @Override. זהו אחד משלושת ההערות המובנות ב-Java ואתה תמיד צריך להשתמש בו בעת עקיפת שיטות.

קומפוזיציה כישועה

אז מה אם נרצה להשתמש בפונקציית methodA()מחלקה ClassAוכיתה ב ? פתרון לכך יכול להיות קומפוזיציה - גרסה משוכתבת המיישמת את שתי שיטות המחלקה ויש לה גם מימוש לאחד האובייקטים. methodB() ClassB ClassС ClassC ClassA ClassB doSomething() ClassC.java
package com.journaldev.inheritance;

public class ClassC{

    ClassA objA = new ClassA();
    ClassB objB = new ClassB();

    public void test(){
        objA.doSomething();
    }

    public void methodA(){
        objA.methodA();
    }

    public void methodB(){
        objB.methodB();
    }
}

הרכב או ירושה?

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

    ClassC.java

    package com.journaldev.inheritance;
    
    public class ClassC{
    
    public void methodC(){
      	}
    
    }

    ClassD.java

    package com.journaldev.inheritance;
    
    public class ClassD extends ClassC{
    
        public int test(){
            return 0;
        }
    }

    הקוד שלמעלה מקמפל ועובד מצוין, אבל מה אם ClassCהוא היה מיושם אחרת:

    package com.journaldev.inheritance;
    
    public class ClassC{
    
        public void methodC(){
        }
    
        public void test(){
        }
    }

    שימו לב שהשיטה test()כבר קיימת במחלקה הצאצאית, אך מחזירה תוצאה מסוג אחר. כעת ClassD, במקרה שאתה משתמש ב-IDE, הוא לא יתבצע קומפילציה. יומלץ לך לשנות את סוג ההחזרה בכיתה צאצא או על.

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

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

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

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

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

    ClassC.java

    package com.journaldev.inheritance;
    public class ClassC{
        SuperClass obj = null;
        public ClassC(SuperClass o){
            this.obj = o;
        }
        public void test(){
            obj.doSomething();
        }
    
        public static void main(String args[]){
            ClassC obj1 = new ClassC(new ClassA());
            ClassC obj2 = new ClassC(new ClassB());
    
            obj1.test();
            obj2.test();
        }
    }

    התוכנית למעלה תציג:

    doSomething implementation of A
    doSomething implementation of B

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

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

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

מאמר מקורי
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION