Abstract Classes — البرمجة الكائنية
الأصناف المجردة (Abstract Classes)
أحياناً تريد صنفاً أباً يحدد ما يجب أن يفعله كل ابن، دون أن يكون له معنى إنشاؤه وحده. "شكل" عام ليس له مساحة؛ الدائرة والمربع لهما. الصنف المجرد هو عقد: كل صنف يرث منه ملزم بتنفيذ دوال معينة.
abc و @abstractmethod
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
def describe(self): # دالة عادية مشتركة
return f"المساحة: {self.area():.2f}"ماذا تضيف فعلياً؟
1. لا يمكن إنشاء الصنف المجرد: Shape() ترمي TypeError.
2. لا يمكن إنشاء ابن لم ينفذ كل الدوال المجردة:
class Circle(Shape):
pass
Circle() # TypeError: Can't instantiate abstract class Circle ...الخطأ يظهر عند الإنشاء وليس لاحقاً عند استدعاء الدالة الناقصة، فتكتشف النسيان مبكراً.
مقارنة مع raise NotImplementedError
في درس تعدد الأشكال كتبنا raise NotImplementedError في دالة الأب. هذا يعمل، لكن الخطأ يظهر فقط عند استدعاء الدالة، وقد يكون ذلك بعد ساعات من التشغيل. ABC تكتشفه فوراً.
الدوال العادية في الصنف المجرد
الصنف المجرد يمكن أن يحتوي دوالاً كاملة تعتمد على المجردة (مثل describe أعلاه): الأب يحدد الخطوات العامة، والأبناء يملؤون التفاصيل.
متى تستخدمه؟
- عندما تكتب إطاراً أو مكتبة وتريد إجبار المستخدمين على تنفيذ دوال معينة.
- عندما يكون لديك عدة تنفيذات لنفس المفهوم (مصادر بيانات: ملف، قاعدة بيانات، API).
Duck typing أم ABC؟
في Python كثيراً ما تكفي duck typing. استخدم ABC عندما يكون وجود العقد بوضوح مهماً، أو عندما تكون الأخطاء الناتجة عن دالة ناقصة مكلفة.
تمرين
أنشئ صنفاً مجرداً Notifier بدالة مجردة send(message) ودالة عادية notify_all(users, message)، ثم EmailNotifier و SmsNotifier. جرّب إنشاء صنف ابن لا ينفذ send ولاحظ الخطأ.
from abc import ABC, abstractmethod
# Abstract Class
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def speak(self):
"""يجب أن ينفذها كل حيوان"""
pass
@abstractmethod
def move(self):
"""يجب أن ينفذها كل حيوان"""
pass
# دالة عادية (غير مجردة)
def info(self):
return f"أنا {self.name}"
# لا يمكن إنشاء Animal مباشرة
# animal = Animal("x") # TypeError!
class Dog(Animal):
def speak(self):
return "هاو هاو!"
def move(self):
return "يمشي على 4 أرجل"
class Bird(Animal):
def speak(self):
return "تغريد!"
def move(self):
return "يطير بجناحيه"
# الآن يمكن الإنشاء
dog = Dog("بادي")
bird = Bird("تويتي")
print(dog.speak()) # هاو هاو!
print(bird.move()) # يطير بجناحيه
print(dog.info()) # أنا بادي
# واجهة للأشكال
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
@property
@abstractmethod
def name(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self._width = width
self._height = height
@property
def name(self):
return "مستطيل"
def area(self):
return self._width * self._height
def perimeter(self):
return 2 * (self._width + self._height)
rect = Rectangle(4, 5)
print(f"{rect.name}: المساحة={rect.area()}, المحيط={rect.perimeter()}")
# التحقق
print(isinstance(rect, Shape)) # True
print(isinstance(rect, Rectangle)) # Trueاختبر فهمك