تخطَّ إلى المحتوى

٢١ يوليو ٢٠٢٦

الفرق بين Composition و Inheritance في البرمجة الكائنية

شارك المقال:

من أكثر أسئلة مقابلات البرمجة كائنية التوجّه تكرارًا: متى تستخدم الوراثة (Inheritance)، ومتى التكوين (Composition)؟ والقاعدة الشائعة "فضّل التكوين على الوراثة" (Favor Composition Over Inheritance) تسمعها كثيرًا بدون شرح حقيقي لسببها. هذا المقال يشرحها بمثال واحد فقط.

الفكرة في سطرين

  • الوراثة (is-a): فئة فرعية هي نوع من الفئة الأب، وترث كل سلوكها تلقائيًا. علاقة ثابتة تُحدَّد وقت كتابة الكود.
  • التكوين (has-a): الكائن يملك أو يستخدم كائنات سلوك أخرى بدل أن يرثها، ويقدر يبدّلها وقت التشغيل.

مشكلة الوراثة العميقة

تخيل نظام مركبات: سيارة عادية، سيارة كهربائية، وسيارة برمائية تقدر تسبح كمان.

// البداية بسيطة
class Vehicle {
  drive() { console.log("أقود على الطريق"); }
}

class ElectricVehicle extends Vehicle {
  charge() { console.log("أشحن البطارية"); }
}

// ثم تحتاج سيارة تقدر تطفو... أين تضعها بالشجرة؟
class AmphibiousVehicle extends ElectricVehicle {
  swim() { console.log("أسبح في الماء"); }
}

يبدو منطقيًا، لكن ماذا لو احتجت سيارة برمائية غير كهربائية (بمحرك بنزين)؟ لا يوجد مكان مناسب لها بالشجرة الحالية—إما تكرر كود swim() في فئة منفصلة، أو تعيد هيكلة الشجرة كاملة. المشكلة الجذرية: الوراثة تفترض أن كل قدرة (charge, swim) مرتبطة بمستوى واحد ثابت في تسلسل هرمي، بينما القدرات الحقيقية تُخلط وتُطابق بحرية أكبر من ذلك.

الحل — تكوين قدرات مستقلة

بدل أن ترث القدرات، اجعل كل قدرة كائنًا مستقلًا يُحقَن في المركبة:

// كل قدرة كائن مستقل، لا علاقة وراثة بينها
const electricEngine = {
  charge: () => console.log("أشحن البطارية"),
};

const swimCapability = {
  swim: () => console.log("أسبح في الماء"),
};

class Vehicle {
  constructor(...capabilities) {
    Object.assign(this, ...capabilities); // تركيب القدرات بالكائن
  }

  drive() {
    console.log("أقود على الطريق");
  }
}

// سيارة كهربائية عادية
const tesla = new Vehicle(electricEngine);
tesla.drive();
tesla.charge();

// سيارة برمائية كهربائية — تركيب حر لأي قدرات تحتاجها
const amphibiousTesla = new Vehicle(electricEngine, swimCapability);
amphibiousTesla.swim();

// سيارة برمائية بمحرك بنزين — بدون charge() نهائيًا، بلا أي تكرار كود
const amphibiousGas = new Vehicle(swimCapability);
amphibiousGas.swim();

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

متى تستخدم كل واحد؟

السؤالالوراثة (Inheritance)التكوين (Composition)
العلاقة"هو نوع من" (is-a) ثابتة"يملك" أو "يقدر يفعل" (has-a)
المرونةتسلسل هرمي محدَّد وقت الكتابةتركيب/تبديل وقت التشغيل
تعدد القدرات المستقلةيحتاج تعدد وراثة (غير مدعوم بمعظم اللغات) أو شجرة معقدةيجمع أي عدد من القدرات بلا قيود
مثال مناسبSquare extends Shape (مربع دائمًا شكل)مركبة تملك قدرة سباحة أو شحن حسب الحاجة

القاعدة العملية: ابدأ بالتكوين افتراضيًا، ولا تلجأ للوراثة إلا لعلاقة "هو نوع من" واضحة لن تحتاج كسرها لاحقًا.

علاقتها بأنماط التصميم

هذا المبدأ ليس نظريًا فقط—هو الأساس وراء عدة أنماط تصميم شهيرة: نمط Strategy يحقن خوارزمية كسلوك منفصل بدل فئة فرعية لكل خوارزمية، ونمط Decorator يضيف سلوكًا بتغليف كائن بدل وراثته. لو تبغى تتعمق في هذه الأنماط بأمثلة كاملة، مسار أنماط التصميم بالعربي يغطّيها جميعًا خطوة بخطوة.

الخلاصة

الوراثة قوية لكنها جامدة: علاقة واحدة، تُحدَّد مبكرًا، يصعب تعديلها. التكوين أكثر مرونة: تجمع أي عدد من السلوكيات المستقلة وتبدّلها براحتك. اسأل نفسك دائمًا: هل هذه فعلاً علاقة "هو نوع من"، أم مجرد "يقدر يفعل"؟ الجواب يحدد الأداة الصحيحة.

📚 مصادر رسمية للتعمّق: freeCodeCamp — مصدر تعلّم البرمجة

الأسئلة الشائعة

هل هذا يعني أن الوراثة سيئة دائمًا؟

لا. الوراثة أداة صحيحة لعلاقة 'هو نوع من' واضحة وثابتة لا تتغير مستقبلاً — مثل أن كل مربع مستطيل. المشكلة تبدأ لما تُستخدم لمجرد مشاركة كود بين كلاسات لا تربطها علاقة نوعية حقيقية، فتتحول شجرة الوراثة لقيد يصعب تعديله.

كيف أعرف عمليًا إذا يجب أن أستخدم Composition بدل Inheritance؟

اسأل: هل العلاقة 'هو نوع من' (is-a) أم 'يملك' أو 'يقدر يفعل' (has-a / can-do)؟ سيارة كهربائية 'هي نوع من' سيارة (وراثة منطقية)، لكن 'القدرة على الطفو' ليست نوعًا من السيارة بل سلوك تملكه بعض السيارات فقط (تكوين).

ما علاقة هذا بأنماط التصميم مثل Strategy؟

أنماط Strategy وDecorator وState كلها تطبيقات مباشرة لمبدأ Composition Over Inheritance: بدل فئة فرعية جديدة لكل سلوك ممكن، تحقن كائن سلوك منفصل وتبدّله وقت التشغيل. راجع مسار أنماط التصميم للتفاصيل والأمثلة الكاملة.

اقرأ أيضًا

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