Kodax

تعدد الأشكال (Polymorphism) — البرمجة الكائنية

تعدد الأشكال (Polymorphism)

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

class Circle:
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14159 * self.r ** 2

class Rectangle:
    def __init__(self, w, h):
        self.w, self.h = w, h
    def area(self):
        return self.w * self.h

shapes = [Circle(2), Rectangle(3, 4)]
for shape in shapes:
    print(shape.area())   # كل شكل يحسب مساحته بطريقته

بدون تعدد الأشكال

for shape in shapes:
    if isinstance(shape, Circle):
        area = 3.14 * shape.r ** 2
    elif isinstance(shape, Rectangle):
        area = shape.w * shape.h
    # ... وكلما أضفت شكلاً عدّلت هذا الكود

مع تعدد الأشكال، إضافة شكل جديد تعني صنفاً جديداً فقط دون لمس الكود الذي يستخدم الأشكال.

عن طريق الوراثة

الصنف الأب يحدد الدالة، وكل ابن يعيد تعريفها:

class Shape:
    def area(self):
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14159 * self.r ** 2

Duck Typing: أسلوب Python

"إذا كان يمشي كالبطة ويصدر صوت البطة، فهو بطة". Python لا تتحقق من النوع، بل من وجود الدالة عند استدعائها:

class Dog:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "هاو"

class Robot:
    def speak(self):
        return "بيب بوب"

for thing in [Dog("ريكس"), Robot()]:
    print(thing.speak())   # لا علاقة وراثة بينهما، ويعمل

أمثلة من Python نفسها: len() تعمل مع أي كائن فيه __len__، و for تعمل مع أي كائن قابل للتكرار.

تعدد الأشكال في العمليات

+ تجمع الأرقام وتدمج النصوص والقوائم: نفس العامل، سلوك مختلف حسب النوع. ستتعلم في درس الدوال السحرية كيف تجعل أصنافك تدعم + و ==.

نقطة مهمة

لكي يعمل تعدد الأشكال، يجب أن تتفق الأصناف على نفس اسم الدالة ونفس المعاملات ونفس معنى الناتج. إذا كانت area() ترجع رقماً في صنف ونصاً في آخر، سينكسر الكود العام.

تمرين

أنشئ أصناف دفع CashPayment و CardPayment و WalletPayment، لكل منها دالة pay(amount) ترجع رسالة مختلفة، ودالة واحدة checkout(payment, amount) تعمل مع أي منها.

# Method Overriding
class Shape:
    def area(self):
        pass
    
    def describe(self):
        return f"مساحتي: {self.area()}"

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14159 * self.radius ** 2

# Polymorphism في العمل
shapes = [Rectangle(4, 5), Circle(3), Rectangle(2, 8)]
for shape in shapes:
    print(shape.describe())

# Duck Typing
class Dog:
    def speak(self):
        return "هاو هاو!"

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

class Robot:
    def speak(self):
        return "beep boop"

# لا يهم النوع، المهم أن لديه speak()
def make_speak(thing):
    print(thing.speak())

make_speak(Dog())
make_speak(Cat())
make_speak(Robot())

# Operator Overloading
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"
    
    def __repr__(self):
        return self.__str__()

v1 = Vector(2, 3)
v2 = Vector(1, 4)
print(v1 + v2)  # Vector(3, 7)
print(v1 - v2)  # Vector(1, -1)
print(v1 * 3)   # Vector(6, 9)
print(v1 == v2) # False
اختبر فهمك