التركيب بدلاً من الوراثة
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 41 من 70.
يؤدي التوريث إلى إنشاء اقتران وثيق بين الفئات. عندما يرث Car من Vehicle، فإن أي تغيير في Vehicle يمكن أن يؤدي إلى تعطل Car. يوفر التركيب بديلاً أكثر مرونة: فبدلاً من وراثة السلوك، تحتوي الفئة على كائنات توفر ذلك السلوك.
تخيل شخصية في لعبة يمكنها التحرك والهجوم. فباستخدام الوراثة (inheritance)، قد تضطر لإنشاء تسلسل هرمي معقد. أما باستخدام التركيب (composition)، فببساطة تمنح الشخصية المكونات التي تحتاجها:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}الفرق الجوهري هو العلاقة. تمثل الوراثة (Inheritance) علاقة "is-a" (الكلب هو حيوان)، بينما يمثل التركيب (Composition) علاقة "has-a" (الشخصية لديها سلوك حركة). يتيح لك التركيب تبديل السلوكيات في وقت التشغيل (runtime)، ودمج القدرات بحرية، وتجنب مشكلة الفئة الأساسية الهشة (fragile base class problem).
استخدم الوراثة عندما تكون هناك علاقة هرمية حقيقية. اختر التركيب عندما تحتاج إلى المرونة، أو عندما قد يتم مشاركة السلوكيات عبر فئات غير مرتبطة، أو عندما تريد تغيير السلوك ديناميكيًا.
التحدي
سهللنقم ببناء نظام إشعارات يوضح قوة التركيب (composition) على الوراثة (inheritance). بدلاً من إنشاء تسلسل هرمي معقد لفئات الإشعارات، ستقوم بتركيب NotificationSender من خلال تزويده بمكونات سلوك قابلة للتبديل.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
Behaviors.cs: قم بإنشاء فئتي سلوك (behavior classes) في مساحة الأسماءNotificationsتتعاملان مع جوانب مختلفة من إرسال الإشعارات:FormattingBehavior- تحتوي على طريقةFormat(string message)تعيد الرسالة مغلفة بـ[ALERT]في البداية و[END]في النهاية.DeliveryBehavior- تحتوي على طريقةDeliver(string channel, string message)تعيد سلسلة نصية بالتنسيقSending via {channel}: {message}.
NotificationSender.cs: قم بإنشاء فئةNotificationSenderفي مساحة الأسماءNotificationsتستخدم التركيب للجمع بين السلوكيات. يجب أن يقوم المرسل الخاص بك بـ:- احتواء مثيلات (instances) من كل من
FormattingBehaviorوDeliveryBehaviorكحقول خاصة (private fields). - امتلاك خاصية
Channel(سلسلة نصية) تخزن اسم قناة التسليم. - امتلاك منشئ (constructor) يقبل اسم القناة.
- امتلاك طريقة
Send(string message)تقوم أولاً بتنسيق الرسالة باستخدام سلوك التنسيق، ثم تسليمها باستخدام سلوك التسليم، وتعيد النتيجة النهائية.
- احتواء مثيلات (instances) من كل من
Program.cs: في ملفك الرئيسي، قم بإنشاءNotificationSenderبقناة مأخوذة من المدخلات، ثم أرسل رسالة مأخوذة من المدخلات. اطبع النتيجة لتوضيح كيفية عمل السلوكيات المركبة معاً.
ستتلقى مدخلين:
- اسم قناة التسليم (مثل
Email،SMS) - الرسالة المراد إرسالها
اطبع نتيجة استدعاء Send() على مرسل الإشعارات الخاص بك.
على سبيل المثال، إذا كانت المدخلات هي Email و Server is down، يجب أن تكون المخرجات:
Sending via Email: [ALERT] Server is down [END]لاحظ كيف أن NotificationSender لا يرث من أي شيء - إنه ببساطة "يمتلك" سلوكيات التنسيق والتسليم. يعني نهج التركيب هذا أنه يمكنك بسهولة تبديل أي من السلوكين دون تغيير فئة المرسل، أو إعادة استخدام هذه السلوكيات في فئات مختلفة تماماً!
ورقة مرجعية
التكوين (Composition) هو بديل للوراثة (inheritance) ينشئ كوداً أكثر مرونة من خلال جعل الفئة (class) تحتوي على كائنات توفر السلوك، بدلاً من وراثة السلوك من فئة أب (parent class).
الوراثة مقابل التكوين:
- تمثل الوراثة علاقات "is-a" (الكلب هو حيوان)
- يمثل التكوين علاقات "has-a" (الشخصية لديها سلوك حركة)
فوائد التكوين:
- تبديل السلوكيات أثناء وقت التشغيل (runtime)
- دمج القدرات بحرية
- تجنب الاقتران الوثيق (tight coupling) ومشاكل الفئة الأساسية الهشة (fragile base class)
- مشاركة السلوكيات عبر فئات غير مرتبطة
مثال مع مكونات السلوك:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}متى تستخدم كل نهج:
- استخدم الوراثة للعلاقات الهرمية الحقيقية
- اختر التكوين للمرونة، أو السلوكيات المشتركة، أو تغييرات السلوك الديناميكية
جرّب بنفسك
using System;
using Notifications;
class Program
{
public static void Main(string[] args)
{
// قراءة المدخلات
string channel = Console.ReadLine();
string message = Console.ReadLine();
// TODO: إنشاء NotificationSender مع القناة
// TODO: إرسال الرسالة وطباعة النتيجة
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection