التركيب مقابل الوراثة
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 76 من 104.
عند تصميم العلاقات بين الأصناف (classes)، لديك خياران رئيسيان: الوراثة ("is-a") و التركيب ("has-a"). يؤثر اختيار النهج الصحيح بشكل كبير على مرونة الكود وقابليته للصيانة.
تنشئ الوراثة ارتباطاً وثيقاً بين الفئات. فالفئة Car التي ترث من Vehicle مرتبطة بشكل دائم بتلك العلاقة:
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};التركيب (Composition) يُدرج الكائنات كأعضاء، مما يخلق علاقة أكثر مرونة. الـ Car يمتلك Engine بدلاً من أن يكون محركاً:
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // السيارة لديها محرك (Car HAS an Engine)
public:
void start() { engine.start(); }
};الميزة الأساسية للتركيب (composition) هي المرونة. يمكنك بسهولة تبديل المكونات، أو تغيير السلوك أثناء وقت التشغيل (runtime)، أو دمج قدرات متعددة دون قيود التسلسل الهرمي الصارم للفئات (class hierarchy). يفضل تصميم ++C الحديث التركيب على الوراثة (inheritance) في معظم الحالات.
استخدم الوراثة عندما تكون هناك علاقة "is-a" حقيقية وتحتاج إلى تعدد الأشكال (polymorphism). استخدم التركيب عندما تريد إعادة استخدام الوظائف أو عندما يتم وصف العلاقة بشكل أفضل على أنها "has-a" أو "uses-a".
التحدي
سهللنقم ببناء محاكي لنظام كمبيوتر يوضح متى يجب استخدام التركيب (composition) مقابل الوراثة (inheritance). ستقوم بنمذجة جهاز كمبيوتر يحتوي على مكونات (التركيب) بدلاً من أن يكون مكوناً، مما يوضح كيف يوفر التركيب المرونة لتبديل الأجزاء في وقت التشغيل.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
Components.h: تحديد المكونات الفردية التي يمكن أن يحتوي عليها الكمبيوتر.قم بإنشاء فئة
CPUمع سمات خاصةstd::string modelوint cores. قم بتوفير منشئ (constructor) يأخذ كلتا القيمتين، وطريقةprocess()تطبعCPU [model] processing with [cores] cores، وطريقةgetModel().قم بإنشاء فئة
Memoryمع سمة خاصةint sizeGB. قم بتوفير منشئ، وطريقةload()تطبعLoading data into [sizeGB]GB RAM، وطريقةgetSize().قم بإنشاء فئة
Storageمع سمات خاصةstd::string type(مثل "SSD" أو "HDD") وint capacityGB. قم بتوفير منشئ، وطريقةread()تطبعReading from [capacityGB]GB [type]، وطرق للحصول على (getter methods) لكلا الحقلين.Computer.h: تحديد فئةComputerالتي تستخدم التركيب لدمج المكونات.يجب أن يحتوي
Computerالخاص بك على كائنات أعضاء خاصة:CPU، وMemory، وStorage. استخدم قائمة تهيئة المنشئ (constructor initialization list) لتهيئة هذه المكونات من المعلمات.قم بتنفيذ هذه الطرق:
boot()— تطبعBooting computer...، ثم تستدعيload()على الذاكرة، وread()على التخزين، وprocess()على المعالج، كل منها في سطر منفصل.specs()— تطبع مواصفات الكمبيوتر بهذا التنسيق:Computer Specs: - CPU: [model] ([cores] cores) - RAM: [size]GB - Storage: [capacity]GB [type]
main.cpp: قراءة ستة مدخلات (كل منها في سطر منفصل):- اسم طراز المعالج (string)
- عدد نوى المعالج (integer)
- حجم الذاكرة بالجيجابايت (integer)
- نوع التخزين (string، مثل "SSD")
- سعة التخزين بالجيجابايت (integer)
- أمر: إما "boot" أو "specs"
قم بإنشاء كائنات المكونات، ثم قم بإنشاء
Computerباستخدام التركيب. بناءً على الأمر، قم باستدعاء إماboot()أوspecs().
على سبيل المثال، مع المدخلات Intel i7، و 8، و 16، و SSD، و 512، و boot:
Booting computer...
Loading data into 16GB RAM
Reading from 512GB SSD
CPU Intel i7 processing with 8 coresمع المدخلات AMD Ryzen، و 6، و 32، و HDD، و 1000، و specs:
Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDDلاحظ كيف أن فئة Computer لا ترث من أي مكون — بل تحتوي عليها. تعني علاقة "has-a" هذه أنه يمكنك بسهولة إنشاء أجهزة كمبيوتر بمجموعات مكونات مختلفة، أو حتى تبديل المكونات لاحقاً إذا أضفت طرق تعيين (setter methods). هذه المرونة هي الميزة الرئيسية للتركيب على الوراثة.
ورقة مرجعية
عند تصميم العلاقات بين الأصناف (classes)، اختر بين الوراثة ("is-a") والتركيب ("has-a").
الوراثة تنشئ ترابطاً وثيقاً بين الأصناف:
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};التركيب يدمج الكائنات كأعضاء، مما يخلق علاقات مرنة:
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // السيارة تمتلك محركاً (Car HAS an Engine)
public:
void start() { engine.start(); }
};متى تستخدم كلاً منهما:
- استخدم الوراثة لعلاقات "is-a" الحقيقية عندما تحتاج إلى تعدد الأشكال (polymorphism)
- استخدم التركيب لإعادة استخدام الوظائف أو لعلاقات "has-a"/"uses-a"
- يفضل تصميم C++ الحديث التركيب لمرونته—يمكنك تبديل المكونات، وتغيير السلوك في وقت التشغيل، ودمج القدرات دون تسلسلات هرمية جامدة
جرّب بنفسك
#include <iostream>
#include <string>
#include "Computer.h"
using namespace std;
int main() {
// قراءة المدخلات
string cpuModel;
getline(cin, cpuModel); // اسم طراز الـ CPU (قد يحتوي على مسافات)
int cpuCores;
cin >> cpuCores; // عدد أنوية الـ CPU
int memorySize;
cin >> memorySize; // حجم الذاكرة بالـ GB
string storageType;
cin >> storageType; // نوع التخزين (مثل "SSD")
int storageCapacity;
cin >> storageCapacity; // سعة التخزين بالـ GB
string command;
cin >> command; // الأمر: "boot" أو "specs"
// TODO: إنشاء كائنات المكونات (CPU، Memory، Storage)
// TODO: إنشاء كائن Computer باستخدام الـ composition
// TODO: بناءً على الأمر، استدعِ إما boot() أو specs()
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة