الـ Composition مقابل الـ Inheritance
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 47 من 87.
تنشئ الوراثة علاقة «is-a»: إذ إن Dog هو Animal. ولكن ماذا لو احتاجت فئة ما إلى وظائف من شيء ليست هي إياه؟ هنا يأتي دور composition، إذ تنشئ بدلاً من ذلك علاقة «has-a».
مع التركيب، يحتوي الفئة على مثيلات من فئات أخرى بوصفها حقولًا بدلًا من توسيعها:
// نهج الوراثة: Car هي Engine (غير منطقي)
class Car extends Engine { }
// نهج التركيب: Car تمتلك Engine (منطقي)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}يوفّر Composition العديد من المزايا. يمكنك تغيير الكائن المركّب في وقت التشغيل، مثل تبديل المحركات.
يمكنك أيضًا دمج السلوكيات من عدة فئات، وهو أمر لا يتيحه inheritance في Java. ولا تكشف الفئة المركبة إلا عن methods التي تختارها، مما يوفر تغليفًا أفضل.
إرشاد عملي: استخدم Inheritance عندما تكون هناك علاقة حقيقية من نوع «is-a» وتُمثّل الفئة الفرعية حقًا إصدارًا متخصصًا من الفئة الأصلية.
استخدم composition عندما تحتاج إلى استخدام وظائف class أخرى من دون أن تكون من ذلك النوع. يتبع العديد من المطورين ذوي الخبرة المبدأ التالي: "favor composition over inheritance" لأنه يؤدي إلى تعليمات برمجية أكثر مرونة وقابلية للصيانة.
التحدي
سهللنَبْنِ نظام كمبيوتر باستخدام composition لتوضيح علاقة "has-a". بدلًا من أن يرث Computer من مكوّناته (وهو ما لن يكون منطقيًا)، سيحتوي الكمبيوتر على الأجزاء التي يحتاج إليها ليعمل.
ستنظّم كودك عبر ثلاثة ملفات:
Processor.java: أنشئ class تمثّل وحدة CPU. تحتوي Processor على حقليbrandوspeedGHzخاصّين: الأول من النوع String والثاني من النوع double. أضف constructor لتهيئة الحقلين، وgetter methods لكل منهما. أضف method باسمprocess()تُرجع String بالشكل التالي:[brand] processing at [speedGHz] GHzMemory.java: أنشئ class تمثّل ذاكرة RAM. تحتوي Memory على حقليtypeوsizeGBخاصّين: الأول من النوع String (مثل "DDR4" أو "DDR5") والثاني من النوع int. أضف constructor وgetters، بالإضافة إلى method باسمload()تُرجع:Loading [sizeGB]GB [type] memoryComputer.java: هنا تتألق composition! يجب أن تحتوي class Computer على Processor وMemory بوصفهما حقلي fields خاصّين. فهي لا تمدّهما، بل تحتويهما. أضف constructor يقبل كلا المكوّنين. أضف method باسمboot()تُرجع String متعددة الأسطر توضّح بدء تشغيل الكمبيوتر باستخدام مكوّناته:وأضف أيضًا method باسمBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()تُرجع:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: اجعل نظامك المؤلَّف يعمل! ستتلقى أربع inputs: brand المعالج (String)، وسرعة المعالج (double)، ونوع الذاكرة (String)، وحجم الذاكرة (int).أنشئ Processor وMemory باستخدام هذه القيم، ثم compose بينهما داخل Computer. اطبع نتيجة استدعاء
boot()، ثم اطبع نتيجةgetSpecs().
ستتلقى أربع inputs بالترتيب: brand المعالج، وسرعة المعالج (GHz)، ونوع الذاكرة، وحجم الذاكرة (GB).
لاحظ كيف تفوّض Computer العمل إلى مكوّناته بدلًا من محاولة أن تكون هذه المكوّنات. هذه هي قوة composition! يمكن لـ Computer استخدام أي Processor أو Memory تمنحه إياه، مما يجعل التصميم مرنًا وواقعيًا.
جرّب بنفسك
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة المدخلات
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: إنشاء Processor باستخدام العلامة التجارية والسرعة
// TODO: إنشاء Memory باستخدام النوع والحجم
// TODO: إنشاء Computer عن طريق تجميع Processor و Memory
// TODO: طباعة نتيجة boot()
// TODO: طباعة نتيجة getSpecs()
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت