الدرس 2 من 8

الوراثة وتعدد الأشكال

متكتبش نفس الكود مرتين لحاجات شبه بعض.

الوراثة (Inheritance)

في شركة، كل الموظفين ليهم اسم ومرتب. المدير موظف برضو، بس زيادة عليه إنه عنده فريق. بدل ما تكتب كلاس المدير من الأول، بتخليه يورث من كلاس الموظف وتضيف بس الحاجات الجديدة.

company.py
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def monthly_pay(self):
        return self.salary

    def describe(self):
        return f"{self.name} earns {self.monthly_pay():,} EGP"


class Manager(Employee):                 # Manager inherits from Employee
    def __init__(self, name, salary, team_size):
        super().__init__(name, salary)   # reuse the parent's __init__
        self.team_size = team_size

    def monthly_pay(self):               # override: change parent behavior
        bonus = self.team_size * 500
        return self.salary + bonus


staff = [Employee("Omar", 12000), Manager("Nour", 25000, team_size=6)]
for person in staff:
    print(person.describe())
parent الكلاس الأصلي، ويتقال له base class.
child الكلاس اللي بيورث، ويتقال له subclass.
super() بتنادي على نسخة الـ parent من الميثود.
override إنك تكتب ميثود في الـ child بنفس الاسم عشان تغير سلوكها.

لاحظ إن describe() مكتوبة مرة واحدة بس في Employee، ومع ذلك مع نور حسبت البونص. ده لأنها بتنادي self.monthly_pay()، و self هنا مدير، فبايثون استخدمت نسخة المدير.

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

الكلمة شكلها صعب بس الفكرة بسيطة: أوبجكتس مختلفة بترد على نفس الأمر كل واحد بطريقته. زي لما تقول لأي حد «ادفع»: واحد هيدفع كاش، وواحد فيزا، وواحد بمحفظة موبايل. انت مش فارق معاك هو هيدفع إزاي، المهم يدفع.

payments.py
class Cash:
    def pay(self, amount):
        return f"Paid {amount} EGP in cash"

class Card:
    def pay(self, amount):
        fee = amount * 0.01
        return f"Paid {amount} EGP by card (+{fee:.0f} fee)"

class MobileWallet:
    def __init__(self, number):
        self.number = number
    def pay(self, amount):
        return f"Paid {amount} EGP from wallet {self.number}"

def checkout(method, total):
    # This function doesn't care WHICH payment type it gets
    print(method.pay(total))

for m in [Cash(), Card(), MobileWallet("010xxxxxxx")]:
    checkout(m, 450)
🌍 ليه ده مهم في الشغل الحقيقي؟لو بكرة ضفت طريقة دفع جديدة زي InstaPay، هتعمل كلاس جديد فيه pay وخلاص. دالة checkout مش هتتغير خالص. الكود بيكبر من غير ما يتكسر.

التغليف (Encapsulation)

مش كل حاجة جوه الأوبجكت المفروض تتلعب فيها من برا. في بايثون العرف إن أي attribute بيبدأ بـ _ (زي _balance) معناه «ده للاستخدام الداخلي، متلمسوش مباشرة»، واستخدم الميثودز اللي الكلاس عاملها.

encapsulation.py
class Wallet:
    def __init__(self):
        self._balance = 0           # "internal" by convention

    def add(self, amount):
        if amount <= 0:
            raise ValueError("Amount must be positive")
        self._balance += amount

    def get_balance(self):
        return self._balance

w = Wallet()
w.add(200)
print(w.get_balance())
💡 raiseraise بتطلّع خطأ بنفسك لما حاجة غلط تحصل. اللي بيستخدم الكلاس يقدر يمسكه بـ try / except زي ما اتعلمنا في المرحلة الأولى.
الدرس اللي فات