تعدد الأشكال (Polymorphism) في Python

تخيل جهاز تحكم عن بُعد واحد يعمل مع التلفاز، المكيف، والمروحة. كل جهاز يستجيب لزر "التشغيل" بطريقته الخاصة، لكنك تستخدم نفس الزر! هذا هو تعدد الأشكال (Polymorphism) - القدرة على استخدام واجهة واحدة لأنواع مختلفة من الكائنات. في تعلم البرمجة بالعربي، نتعلم كيف يجعل هذا المفهوم الكود أكثر مرونة وأناقة.

1. تعريف تعدد الأشكال

تعدد الأشكال (Polymorphism) يعني حرفياً "أشكال متعددة". في البرمجة، يشير إلى قدرة الكائنات من أصناف مختلفة على الاستجابة لنفس اسم الدالة بطرق مختلفة.

في دروس برمجة عربية احترافية، نتعلم أن Polymorphism يحقق ثلاثة أهداف رئيسية:

  • المرونة (Flexibility): كتابة كود يعمل مع أنواع مختلفة من الكائنات.
  • قابلية التوسع (Extensibility): إضافة أصناف جديدة دون تعديل الكود القديم.
  • البساطة (Simplicity): استخدام واجهة واحدة بدلاً من عدة واجهات مختلفة.
مثال من حياتنا

عندما تقول "افتح"، يمكن أن تقصد: افتح الباب، افتح الملف، افتح التطبيق، افتح الصندوق. نفس الأمر "افتح" لكن كل شيء يستجيب بطريقته الخاصة!

في البرمجة، نستخدم نفس اسم الدالة (مثل draw()) لكائنات مختلفة (دائرة، مربع، مثلث)، وكل كائن يرسم نفسه بطريقته.

2. أنواع تعدد الأشكال في Python

أ- Method Overriding (إعادة تعريف الدوال)

عندما يعيد الصنف الابن تعريف دالة موجودة في الصنف الأب. رأينا هذا في درس الوراثة.

ب- Duck Typing

مبدأ بايثون الشهير: "إذا كان يمشي مثل البطة ويصدر صوتاً مثل البطة، فهو بطة!" لا يهم نوع الكائن، المهم أن يمتلك الدالة المطلوبة.

ج- Operator Overloading

إعادة تعريف سلوك العمليات الحسابية (مثل + أو *) للعمل مع أصناف مخصصة.

3. أمثلة تطبيقية

المثال الأول: Polymorphism الأساسي

لنبدأ بمثال بسيط يوضح كيف تستجيب كائنات مختلفة لنفس اسم الدالة.

basic_polymorphism.py
class Dog:
    def speak(self):
        return "وف وف! 🐕"

class Cat:
    def speak(self):
        return "مياو مياو! 🐈"

class Cow:
    def speak(self):
        return "موووو! 🐄"

# دالة واحدة تعمل مع أنواع مختلفة
def make_animal_speak(animal):
    print(animal.speak())

# إنشاء حيوانات مختلفة
dog = Dog()
cat = Cat()
cow = Cow()

# نفس الدالة، نتائج مختلفة!
make_animal_speak(dog)
make_animal_speak(cat)
make_animal_speak(cow)

# أو باستخدام حلقة
animals = [Dog(), Cat(), Cow()]
for animal in animals:
    print(animal.speak())
النتيجة
وف وف! 🐕 مياو مياو! 🐈 موووو! 🐄 وف وف! 🐕 مياو مياو! 🐈 موووو! 🐄
شرح الكود سطراً بسطر:
  1. class Dog: - نعرّف صنف الكلب.
  2. def speak(self): - كل حيوان لديه دالة speak بنفس الاسم.
  3. def make_animal_speak(animal): - دالة تقبل أي حيوان.
  4. animal.speak() - نستدعي speak دون معرفة نوع الحيوان!
  5. animals = [Dog(), Cat(), Cow()] - قائمة بأنواع مختلفة.
  6. for animal in animals: - نمر على كل حيوان.
  7. animal.speak() - كل حيوان يتكلم بطريقته!

السحر: دالة واحدة تعمل مع أنواع مختلفة من الكائنات!

المثال الثاني: الأشكال الهندسية (مثال واقعي)

مثال عملي يوضح كيف يسهل Polymorphism التعامل مع أشكال هندسية مختلفة.

shapes_polymorphism.py
class Circle:
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14 * self.radius ** 2
    
    def draw(self):
        print(f"رسم دائرة بنصف قطر {self.radius}")

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def draw(self):
        print(f"رسم مستطيل {self.width}×{self.height}")

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height
    
    def area(self):
        return 0.5 * self.base * self.height
    
    def draw(self):
        print(f"رسم مثلث بقاعدة {self.base} وارتفاع {self.height}")

# دالة تحسب المساحة الكلية لأي أشكال
def calculate_total_area(shapes):
    total = 0
    for shape in shapes:
        total += shape.area()  # كل شكل يحسب مساحته بطريقته!
    return total

# دالة ترسم كل الأشكال
def draw_all_shapes(shapes):
    for shape in shapes:
        shape.draw()  # كل شكل يرسم نفسه بطريقته!

# إنشاء أشكال مختلفة
shapes = [
    Circle(5),
    Rectangle(4, 6),
    Triangle(3, 8)
]

# رسم كل الأشكال
print("=== رسم الأشكال ===")
draw_all_shapes(shapes)

# حساب المساحة الكلية
print(f"\nالمساحة الكلية: {calculate_total_area(shapes):.2f}")
النتيجة
=== رسم الأشكال === رسم دائرة بنصف قطر 5 رسم مستطيل 4×6 رسم مثلث بقاعدة 3 وارتفاع 8 المساحة الكلية: 114.50
شرح الكود سطراً بسطر:
  1. class Circle: - كل شكل هو صنف منفصل.
  2. def area(self): - كل شكل لديه دالة area بنفس الاسم.
  3. def draw(self): - كل شكل لديه دالة draw بنفس الاسم.
  4. def calculate_total_area(shapes): - دالة تقبل قائمة من أي أشكال.
  5. shape.area() - نستدعي area دون معرفة نوع الشكل!
  6. shapes = [Circle(5), Rectangle(4, 6), ...] - قائمة بأشكال مختلفة تماماً.
  7. draw_all_shapes(shapes) - دالة واحدة ترسم كل الأشكال!

القوة: يمكننا إضافة شكل جديد (مثلاً Pentagon) دون تعديل الدوال الموجودة!

المثال الثالث: Duck Typing في بايثون

بايثون لا تهتم بنوع الكائن، فقط تهتم بوجود الدالة المطلوبة. هذا يسمى Duck Typing.

duck_typing.py
# أصناف مختلفة تماماً، لكن كلها لديها دالة fly()
class Bird:
    def fly(self):
        print("الطائر يطير بجناحيه 🦅")

class Airplane:
    def fly(self):
        print("الطائرة تطير بمحركاتها ✈️")

class Superhero:
    def fly(self):
        print("البطل الخارق يطير بقواه الخاصة 🦸")

# دالة تجعل أي شيء يطير!
def make_it_fly(flying_object):
    # لا نسأل: ما نوعك؟
    # نسأل فقط: هل لديك دالة fly؟
    flying_object.fly()

# كائنات مختلفة تماماً
bird = Bird()
plane = Airplane()
hero = Superhero()

# نفس الدالة تعمل مع الجميع!
make_it_fly(bird)
make_it_fly(plane)
make_it_fly(hero)

# حتى لو أضفنا صنفاً جديداً
class Drone:
    def fly(self):
        print("الطائرة بدون طيار تطير بالتحكم عن بعد 🚁")

drone = Drone()
make_it_fly(drone)  # يعمل مباشرة!
النتيجة
الطائر يطير بجناحيه 🦅 الطائرة تطير بمحركاتها ✈️ البطل الخارق يطير بقواه الخاصة 🦸 الطائرة بدون طيار تطير بالتحكم عن بعد 🚁
شرح الكود سطراً بسطر:
  1. class Bird: - أصناف مختلفة تماماً، لا علاقة وراثية بينها.
  2. def fly(self): - لكن كلها تمتلك دالة fly.
  3. def make_it_fly(flying_object): - دالة لا تهتم بنوع الكائن.
  4. flying_object.fly() - تستدعي fly مباشرة دون فحص النوع!
  5. class Drone: - يمكننا إضافة أصناف جديدة في أي وقت.
  6. make_it_fly(drone) - تعمل مباشرة دون تعديل الدالة!

Duck Typing: "إذا كان يمشي مثل البطة ويصدر صوتاً مثل البطة، فهو بطة!"

المثال الرابع: Operator Overloading (إعادة تعريف العمليات)

يمكننا إعادة تعريف سلوك العمليات الحسابية للعمل مع أصنافنا الخاصة.

operator_overloading.py
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # إعادة تعريف عملية الجمع +
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
    
    # إعادة تعريف عملية الضرب *
    def __mul__(self, scalar):
        return Point(self.x * scalar, self.y * scalar)
    
    # إعادة تعريف طريقة الطباعة
    def __str__(self):
        return f"({self.x}, {self.y})"
    
    # إعادة تعريف المقارنة ==
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

# إنشاء نقاط
p1 = Point(2, 3)
p2 = Point(4, 5)

# استخدام + مع النقاط!
p3 = p1 + p2
print(f"{p1} + {p2} = {p3}")

# استخدام * مع النقاط!
p4 = p1 * 3
print(f"{p1} × 3 = {p4}")

# استخدام == مع النقاط!
p5 = Point(2, 3)
print(f"{p1} == {p5}: {p1 == p5}")
print(f"{p1} == {p2}: {p1 == p2}")

# مثال عملي: جمع عدة نقاط
points = [Point(1, 2), Point(3, 4), Point(5, 6)]
total = Point(0, 0)
for point in points:
    total = total + point  # نستخدم + كأنها أرقام عادية!
print(f"\nمجموع النقاط: {total}")
النتيجة
(2, 3) + (4, 5) = (6, 8) (2, 3) × 3 = (6, 9) (2, 3) == (2, 3): True (2, 3) == (4, 5): False مجموع النقاط: (9, 12)
شرح الكود سطراً بسطر:
  1. class Point: - صنف يمثل نقطة في المستوى.
  2. def __add__(self, other): - دالة سحرية تُستدعى عند استخدام +.
  3. return Point(self.x + other.x, ...) - نرجع نقطة جديدة بمجموع الإحداثيات.
  4. def __mul__(self, scalar): - دالة سحرية تُستدعى عند استخدام *.
  5. def __str__(self): - تحدد كيف تُطبع النقطة.
  6. def __eq__(self, other): - تحدد كيف نقارن نقطتين.
  7. p3 = p1 + p2 - نستخدم + مع كائناتنا الخاصة!

الدوال السحرية الشائعة: __add__, __sub__, __mul__, __div__, __eq__, __lt__, __str__, __len__

4. فوائد تعدد الأشكال

1. كود أقل تكراراً

بدلاً من كتابة دالة لكل نوع، نكتب دالة واحدة تعمل مع جميع الأنواع.

2. سهولة الصيانة

عند إضافة صنف جديد، لا نحتاج لتعديل الكود القديم.

3. قابلية التوسع

يمكن إضافة أصناف جديدة في أي وقت دون كسر الكود الموجود.

4. كود أكثر طبيعية

استخدام + لجمع النقاط أو الأرقام يبدو طبيعياً وسهل الفهم.

5. أخطاء شائعة

الخطأ 1: نسيان تعريف الدالة في أحد الأصناف

إذا نسيت تعريف speak() في أحد الحيوانات، ستحصل على خطأ عند استدعائها.

الحل: تأكد أن جميع الأصناف تمتلك الدوال المطلوبة.

الخطأ 2: اختلاف أسماء الدوال

استخدام calculate_area() في صنف و get_area() في آخر يكسر Polymorphism.

الحل: استخدم نفس الاسم في جميع الأصناف.

الخطأ 3: اختلاف عدد المعاملات

إذا كانت draw() تأخذ معاملات في صنف ولا تأخذ في آخر، ستحدث مشاكل.

الحل: حافظ على نفس التوقيع (signature) للدالة في جميع الأصناف.

6. نصائح مهمة

استخدم أسماء دوال واضحة

اختر أسماء دوال تعبر عن الفعل بوضوح: draw(), speak(), calculate().

وثّق التوقيع المتوقع

اشرح في التعليقات ما هي الدوال التي يجب أن يمتلكها الكائن ليعمل مع دالتك.

استفد من Duck Typing

لا تفحص نوع الكائن بـ isinstance() إلا عند الضرورة القصوى. ثق أن الكائن يمتلك الدالة المطلوبة.

7. تمرين عملي

تحدي نظام الدفع

قم بإنشاء نظام دفع يدعم طرق دفع مختلفة:

  1. أنشئ صنف CreditCard بدالة pay(amount).
  2. أنشئ صنف PayPal بنفس الدالة.
  3. أنشئ صنف Cash بنفس الدالة.
  4. أنشئ دالة process_payment(payment_method, amount) تعمل مع أي طريقة دفع.
  5. كل طريقة دفع تطبع رسالة مختلفة عند الدفع.
  6. اختبر الدالة مع طرق الدفع الثلاثة.

هذا التمرين سيثبت فهمك لقوة Polymorphism في تبسيط الكود!

ملخص الدرس
  • Polymorphism يسمح باستخدام واجهة واحدة لأنواع مختلفة من الكائنات.
  • Duck Typing: بايثون لا تهتم بنوع الكائن، فقط بوجود الدالة المطلوبة.
  • Operator Overloading يسمح بإعادة تعريف العمليات الحسابية.
  • الدوال السحرية مثل __add__ و __str__ تتحكم في سلوك الكائنات.
  • Polymorphism يجعل الكود أكثر مرونة وقابلية للتوسع.
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن