التركيب مقابل الوراثة (Composition) — البرمجة الكائنية
التركيب مقابل الوراثة
الوراثة علاقة "هو نوع من" (is-a): الكلب حيوان. لكن كثيراً من العلاقات من نوع "يحتوي على" (has-a): السيارة تحتوي محركاً، والطلب يحتوي منتجات. هذه علاقة تركيب (composition): الكائن يحمل كائنات أخرى كخصائص ويستخدمها.
class Engine:
def start(self):
return "المحرك يعمل"
class Car:
def __init__(self):
self.engine = Engine() # السيارة تحتوي محركاً
def start(self):
return self.engine.start() # تفويض العمل للمحركلماذا لا نجعل Car ترث من Engine؟
لأن السيارة ليست محركاً. لو ورثت منه لأصبح لها كل دوال المحرك كأنها دوالها (car.change_oil_filter_gasket())، ولتعذر استبدال المحرك بنوع آخر.
مشكلة شائعة مع الوراثة
لنفترض أصناف Bird و Fish، ثم احتجت FlyingFish و Penguin (طائر لا يطير). شجرة الوراثة تبدأ بالانهيار. مع التركيب تبني الكائن من قدرات:
class Walk: ...
class Swim: ...
class Animal:
def __init__(self, name, movements):
self.name = name
self.movements = movements # قائمة كائنات
penguin = Animal("بطريق", [Walk(), Swim()])القدرات قطع منفصلة تُركّب حسب الحاجة.
ميزة الاستبدال
class PdfExporter:
def export(self, data): return "PDF"
class CsvExporter:
def export(self, data): return "CSV"
class Report:
def __init__(self, exporter):
self.exporter = exporter # أي كائن فيه export()
Report(PdfExporter())
Report(CsvExporter())يمكنك تغيير السلوك عند الإنشاء أو حتى أثناء التشغيل، ويسهل الاختبار بتمرير كائن تجريبي بسيط بدل كائن حقيقي يرسل بريداً أو يتصل بقاعدة بيانات. هذا الأسلوب يسمى حقن الاعتماديات (dependency injection).
القاعدة العملية
"فضّل التركيب على الوراثة" عندما:
- تكون العلاقة "يحتوي" أو "يستخدم".
- تريد تغيير جزء من السلوك بسهولة.
- تجد نفسك تنشئ أصنافاً وراثية لكل تركيبة ممكنة.
واستخدم الوراثة عندما تكون العلاقة "نوع من" حقيقية ومستقرة، وتريد تعدد الأشكال عبر واجهة مشتركة.
تمرين
صمم Order يحتوي قائمة OrderItem (منتج وكمية)، وكائن DiscountPolicy قابلاً للتبديل (بدون خصم، نسبة مئوية، مبلغ ثابت). احسب الإجمالي بعد الخصم دون أي وراثة بين Order وبقية الأصناف.
from dataclasses import dataclass, field
@dataclass
class Product:
name: str
price: float
@dataclass
class OrderItem:
product: Product
quantity: int
@property
def subtotal(self):
return self.product.price * self.quantity
# سياسات خصم قابلة للتبديل (duck typing: كلها فيها apply)
class NoDiscount:
def apply(self, total):
return total
class PercentDiscount:
def __init__(self, percent):
self.percent = percent
def apply(self, total):
return total * (1 - self.percent / 100)
class FixedDiscount:
def __init__(self, amount):
self.amount = amount
def apply(self, total):
return max(0, total - self.amount)
@dataclass
class Order:
items: list = field(default_factory=list) # Order يحتوي عناصر
discount: object = field(default_factory=NoDiscount) # ويحتوي سياسة خصم
def add(self, product, quantity=1):
self.items.append(OrderItem(product, quantity))
def total(self):
raw = sum(item.subtotal for item in self.items)
return round(self.discount.apply(raw), 2)
coffee = Product("قهوة", 4.5)
cake = Product("كعكة", 6.0)
order = Order()
order.add(coffee, 2)
order.add(cake)
print("بدون خصم:", order.total()) # 15.0
order.discount = PercentDiscount(10) # تبديل السلوك أثناء التشغيل
print("خصم 10%:", order.total()) # 13.5
order.discount = FixedDiscount(5)
print("خصم 5:", order.total()) # 10.0
اختبر فهمك