التكوين مقابل التوريث

التركيب مقابل التوريث هو واحد من الأسئلة المتداولة في المقابلات بشكل متكرر. يجب أن تكون قد سمعت أيضًا بضرورة استخدام التركيب على التوريث.

التركيب مقابل التوريث

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

التركيب

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

package com.journaldev.composition;

public class Job {
// متغيرات، وأساليب وما إلى ذلك
}
package com.journaldev.composition;

public class Person {

    // علاقة التركيب "تملك"
    private Job job;

    // متغيرات، وأساليب، وبناة وما إلى ذلك. برمجة موجهة نحو الكائنات

التوريث

التراث هو تقنية التصميم في البرمجة الشيئية لتنفيذ العلاقة “هو” بين الكائنات. يتم تنفيذ التوريث في جافا باستخدام الكلمة المفتاحية extends. على سبيل المثال، ستُنفذ علاقة القط بالحيوان في برمجة جافا كما يلي:

package com.journaldev.inheritance;
 
public class Animal {
// متغيرات، وأساليب وما إلى ذلك.
}
package com.journaldev.inheritance;
 
public class Cat extends Animal{
}

التركيب فوق التوريث

كل من التركيب والتوريث يعززان إعادة استخدام الشفرة من خلال مقاربات مختلفة. فما الذي يجب اختياره؟ كيفية مقارنة التركيب مقابل التوريث. يجب أن تكون قد سمعت في البرمجة أنه يجب أن تفضل التركيب فوق التوريث. دعنا نرى بعض الأسباب التي ستساعدك في اختيار التركيب مقابل التوريث.

  1. التوريث مرتبط تمامًا بينما التكوين مرتبط بشكل فضفاض. لنفترض أن لدينا الفئات التالية مع التوريث.

    package com.journaldev.java.examples;
    
    public class ClassA {
    
    	public void foo(){	
    	}
    }
    
    class ClassB extends ClassA{
    	public void bar(){
    		
    	}
    }
    

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

    package com.journaldev.java.examples;
    
    public class ClassA {
    
    	public void foo(){	
    	}
    	
    	public int bar(){
    		return 0;
    	}
    }
    

    فور استخدام تنفيذ ClassA الجديد، ستحصل على خطأ في وقت الترجمة في ClassB كـ نوع العائد غير متوافق مع ClassA.bar(). الحل سيكون تغيير إما الفئة الأساسية أو طريقة الفئة الفرعية bar() لجعلها متوافقة. إذا كنت قد استخدمت التكوين بدلاً من التوريث، فلن تواجه هذه المشكلة أبدًا. مثال بسيط على تنفيذ ClassB باستخدام التكوين يمكن أن يكون كما يلي.

    class ClassB{
    	ClassA classA = new ClassA();
    	
    	public void bar(){
    		classA.foo();
    		classA.bar();
    	}
    }
    
  2. لا يوجد تحكم في الوصول في الوراثة بينما يمكن تقييد الوصول في التركيب. نعرض جميع طرق الفئة الأساسية للفئات الأخرى التي لها وصول إلى الفئة الفرعية. لذلك ، إذا تم إدخال طريقة جديدة أو إذا كان هناك ثغرات أمان في الفئة الأساسية ، فإن الفئة الفرعية تصبح غير محمية. نظرًا لأننا في التركيب نختار أي طرق نستخدمها ، فإنها أكثر أمانًا من الوراثة. على سبيل المثال ، يمكننا توفير واجهة طريقة ClassA foo() للفئات الأخرى باستخدام الكود التالي في ClassB.

    class ClassB {
    
    	ClassA classA = new ClassA();
    
    	public void foo(){
    		classA.foo();
    	}
    
    	public void bar(){	
    	}
    
    }
    

    هذه هي واحدة من أهم مزايا التركيب على الوراثة.

  3. التكوين يوفر مرونة في استدعاء الأساليب وهو مفيد في سيناريوهات الفئات الفرعية المتعددة. على سبيل المثال، دعونا نفترض أن لدينا السيناريو التالي للتوريث.

    abstract class Abs {
    	abstract void foo();
    }
    
    public class ClassA extends Abs{
    
    	public void foo(){	
    	}
    	
    }
    
    class ClassB extends Abs{
    		
    	public void foo(){
    	}
    	
    }
    
    class Test {
    	
    	ClassA a = new ClassA();
    	ClassB b = new ClassB();
    
    	public void test(){
    		a.foo();
    		b.foo();
    	}
    }
    

    فماذا لو كانت هناك المزيد من الفئات الفرعية، هل ستجعل التكوين كودنا غير جذاب من خلال وجود مثيل واحد لكل فئة فرعية؟ لا، يمكننا إعادة كتابة فئة الاختبار كما هو مبين أدناه.

    class Test {
    	Abs obj = null;
    	
    	Test1(Abs o){
    		this.obj = o;
    	}
    	
    	public void foo(){
    		this.obj.foo();
    	}
    
    }
    

    سيمنحك هذا المرونة لاستخدام أي فئة فرعية استنادًا إلى الكائن المستخدم في المُنشئ.

  4. فائدة أخرى للتركيبة على التوريث هي نطاق الاختبار. الاختبار الوحدوي سهل في التركيب لأننا نعلم بالضبط أي الطرق نستخدم من فئة أخرى. يمكننا محاكاتها للاختبار بينما في التوريث نعتمد بشكل كبير على الفئة الأم ولا نعرف أي الطرق التي ستُستخدم. لذا، سيتعين علينا اختبار جميع طرق الفئة الأم. هذا عمل إضافي وعلينا القيام به دون داع لأن التوريث.

هذا كل شيء عن التركيبة مقابل التوريث. لديك أسباب كافية لاختيار التركيبة على التوريث. استخدم التوريث فقط عندما تكون متأكداً من أن الفئة الأم لن تتغير، وإلا فاذهب للتركيبة.

Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance