JavaRush /مدونة جافا /Random-AR /الوراثة المتعددة في جافا التكوين مقابل الميراث
DSergey_Kh
مستوى

الوراثة المتعددة في جافا التكوين مقابل الميراث

نشرت في المجموعة

الوراثة المتعددة في جافا

يتيح لك الوراثة المتعددة إنشاء فئة ترث من فئات فائقة متعددة. على عكس بعض لغات البرمجة الكائنية الشائعة الأخرى، مثل C++، لا تسمح 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()تستدعي طريقة الطبقة الفائقة doSomething(). يؤدي هذا إلى الغموض لأن المترجم لا يعرف أي طريقة من فئات الطبقة الفائقة يجب تنفيذها. هذا هو مخطط فئة على شكل الماس يسمى مشكلة الماس. هذا هو السبب الرئيسي وراء عدم دعم 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، فسوف يطالبك بتغيير نوع الإرجاع في الفئة الفائقة أو الفئة الفرعية.

    الآن تخيل موقفًا حيث لدينا تسلسل هرمي متعدد المستويات لوراثة الطبقة ولا يمكننا الوصول إلى الطبقة المتفوقة. لن يكون لدينا خيار سوى تغيير توقيع طريقة الفئة الفرعية أو اسمها لإزالة خطأ الترجمة. سيتعين علينا أيضًا تغيير طريقة الفئة الفرعية في جميع الأماكن التي يتم استدعاؤها فيها. وبالتالي، فإن الميراث يجعل كودنا هشًا.

    المشكلة المذكورة أعلاه لن تحدث أبدًا مع التركيب وهذا يجعلها أكثر جاذبية للميراث.

  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. يعد اختبار الوحدة أسهل في إجراء التركيب لأننا نعلم أننا نستخدم جميع الأساليب من الفئة الفائقة ويمكننا نسخها للاختبار. بينما في الميراث نعتمد أكثر على الطبقة الفائقة ولا نعرف جميع أساليب الطبقة الفائقة التي سيتم استخدامها. لذلك يتعين علينا اختبار جميع أساليب الطبقة المتفوقة، وهو عمل إضافي بسبب الميراث.

    من الناحية المثالية، يجب علينا استخدام الميراث فقط عندما يتم تعريف العلاقة بين الفئة الفرعية والطبقة المتفوقة على أنها "هي". في جميع الحالات الأخرى، يوصى باستخدام التركيبة.

تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION