JavaRush /בלוג Java /Random-HE /ירושה מרובה ב-Java. הרכב מול ירושה
DSergey_Kh
רָמָה

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

פורסם בקבוצה

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

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

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

כדי להבין את בעיית היהלומים ביתר קלות, הבה נניח שהירושה המרובה נתמכת ב-Java. במקרה זה, יכול להיות לנו היררכיית מחלקות כפי שמוצג בתמונה למטה. ירושה מרובה ב-Java.  הרכב מול ירושה - 1נניח שהמחלקה SuperClassמופשטת ומוצהרת בה שיטה כלשהי. גם שיעורי בטון ClassAוגם ClassB.
package com.journaldev.inheritance;
public abstract class SuperClass {
	public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of A");
	}
	//ClassA own method
	public void methodA(){
	}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of B");
	}
	//ClassB specific method
	public void methodB(){
	}
}
כעת נניח שאנו רוצים ליישם ClassCולרשת אותו מ- ClassAו ClassB.
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
	public void test(){
		//calling super class method
		doSomething();
	}
}
שימו לב שהשיטה test()קוראת לשיטת superclass doSomething(). זה מוביל לאי בהירות מכיוון שהמהדר לא יודע איזו שיטת superclass לבצע. זהו דיאגרמת מחלקה בצורת יהלום הנקראת בעיית יהלומים. זו הסיבה העיקרית לכך ש-Java לא תומכת בירושה מרובה. שים לב שהבעיה שלעיל עם ירושה מחלקות מרובות יכולה להתרחש רק עם שלוש מחלקות שיש להן לפחות שיטה אחת משותפת.

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

ב-Java, ירושה מרובה אינה נתמכת במחלקות, אך היא נתמכת בממשקים. וממשק אחד יכול להרחיב ממשקים רבים אחרים. להלן דוגמה פשוטה.
package com.journaldev.inheritance;
public interface InterfaceA {
	public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
	public void doSomething();
}
שימו לב ששני הממשקים מצהירים על אותה שיטה. כעת נוכל ליצור ממשק שמרחיב את שני הממשקים הללו, כפי שמוצג בדוגמה למטה.
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
	//same method is declared in InterfaceA and InterfaceB both
	public void doSomething();
}
זה עובד מצוין מכיוון שממשקים מצהירים רק על שיטות והיישום יתבצע במחלקות שיורשות את הממשק. לפיכך, אין דרך לקבל אי בהירות בירושה של ריבוי ממשקים.
package com.journaldev.inheritance;
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
	@Override
	public void doSomething() {
		System.out.println("doSomething implementation of concrete class");
	}
	public static void main(String[] args) {
		InterfaceA objA = new InterfacesImpl();
		InterfaceB objB = new InterfacesImpl();
		InterfaceC objC = new InterfacesImpl();

		//all the method calls below are going to same concrete implementation
		objA.doSomething();
		objB.doSomething();
		objC.doSomething();
	}
}
שים לב שבכל פעם שאתה עוקף שיטת superclass או מיישם שיטת ממשק, השתמש בהערה @Override. מה אם נרצה להשתמש בפונקציה methodA()של מחלקה ClassAובפונקציה methodB()של מחלקה ClassBבכיתה ClassC? הפתרון טמון בשימוש בהרכב. להלן גרסה של המחלקה ClassCהמשתמשת בקומפוזיציה כדי להגדיר הן את שיטות המחלקה והן את המתודה doSomething()של אחד האובייקטים.
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. נניח שיש לנו כיתת-על ושיעור שמרחיב אותו:

    package com.journaldev.inheritance;
    public class ClassC{
    	public void methodC(){
    	}
    }
    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 כלשהו, ​​היא תבקש ממך לשנות את סוג ההחזרה ב- superclass או subclass.

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

    הבעיה שלעיל לעולם לא תתרחש עם הרכב וזה הופך אותו ליותר אטרקטיבי לירושה.

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

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

    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 ויכולים להעתיק אותן למבחן. ואילו בירושה אנחנו תלויים יותר במעמד העל ולא מכירים את כל השיטות של מחלקת העל שישמשו. אז עלינו לבדוק את כל השיטות של מחלקת העל, שהיא עבודה נוספת עקב ירושה.

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

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