JavaRush /مدونة جافا /Random-AR /الوراثة المتعددة في جافا مقارنة التكوين والميراث
HonyaSaar
مستوى
Москва

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

نشرت في المجموعة
منذ بعض الوقت كتبت عدة منشورات حول الميراث والواجهات والتكوين في Java. في هذه المقالة، سنلقي نظرة على الميراث المتعدد ثم نتعرف على فوائد التركيب على الميراث.
الوراثة المتعددة في جافا  مقارنة التكوين والميراث - 1

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

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

    من الناحية المثالية، يجب استخدام الميراث فقط عندما تكون العلاقة " is-a " صحيحة بالنسبة لفئات الوالدين والأبناء، وإلا فيجب تفضيل التركيب.

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