الدرس الخامس

البرمجة كائنية التوجه

في هذا الدرس سنتعلم أسس البرمجة كائنية التوجه (OOP): الفئات والكائنات، الوراثة، تعدد الأشكال، التغليف، والتجريد - المفاهيم الأساسية لبناء برامج منظمة وقابلة للتطوير.

🎯 مقدمة في OOP

البرمجة كائنية التوجه (Object-Oriented Programming) هي نمط برمجي قوي لهيكلة البرامج عن طريق إنشاء "كائنات" (Objects) تغلف بداخلها البيانات (السمات - Attributes) والوظائف (الطرق - Methods).

💡 الهدف من OOP

✅ نمذجة كيانات العالم الحقيقي وتفاعلاتها

✅ جعل الكود أكثر تنظيماً ووضوحاً

✅ قابلية لإعادة الاستخدام (Reusability)

✅ سهولة في الصيانة والتطوير

Class (الفئة)
Blueprint / المخطط
Objects (الكائنات)
Instance 1, Instance 2, Instance 3...

🏗️ الفئات والكائنات (Classes & Objects)

الفئة (Class): هي المخطط (Blueprint) الذي يحدد السمات والوظائف التي ستمتلكها الكائنات.
الكائن (Object): هو نسخة فعلية (Instance) من الفئة، يحمل بيانات خاصة به.

# تعريف الفئة
class Car:
    def __init__(self, brand, model):
        self.brand = brand  # سمة
        self.model = model  # سمة

    def display_info(self):
        # طباعة التفاصيل باستخدام دمج النصوص التقليدي
        print("Car: " + self.brand + " " + self.model)

# إنشاء كائن (Instantiation)
my_car = Car("Toyota", "Corolla")
my_car.display_info()
class Car:
    """Blueprint for creating car objects."""
    def __init__(self, brand: str, model: str):
        self.brand = brand
        self.model = model

    def __str__(self):
        # استخدام دالة __str__ لتمثيل الكائن نصياً
        return f"Car: {self.brand} {self.model}"

# الاستخدام
my_car = Car("Toyota", "Corolla")
print(my_car)  # سيتم استدعاء __str__ تلقائياً
📌 ملاحظة: __init__ هو المشيد (Constructor) - دالة خاصة تُستدعى تلقائياً عند إنشاء كائن جديد. المعامل self يشير إلى الكائن نفسه.

⚙️ سمات الفئة مقابل سمات الكائن

سمات الكائن (Instance Attributes): تُعرف باستخدام self.name، وهي خاصة بكل كائن على حدة.
سمات الفئة (Class Attributes): تُعرف مباشرة داخل الفئة، وهي مشتركة بين جميع الكائنات.

class Car:
    wheels = 4  # سمة مشتركة (للفئة)

    def __init__(self, color):
        self.color = color  # سمة خاصة (للكائن)

car1 = Car("Red")
car2 = Car("Blue")

print(car1.wheels)  # 4
print(car1.color)   # Red
print(car2.color)   # Blue
class Car:
    num_wheels: int = 4  # Class Attribute

    def __init__(self, color: str):
        self.color = color  # Instance Attribute

car1 = Car("Red")
# الوصول لسمة الفئة عبر اسم الفئة لضمان الوضوح
print(f"All cars have {Car.num_wheels} wheels.")
print(f"This car is {car1.color}.")

🧬 الوراثة (Inheritance)

تسمح الوراثة لفئة فرعية (Child Class) باكتساب خصائص وطرق فئة رئيسية (Parent Class). هذا يعزز إعادة استخدام الكود ويدعم العلاقات الهرمية.

class Animal:
    def speak(self):
        print("This animal makes a sound")

# الوراثة: Dog يرث من Animal
class Dog(Animal):
    def speak(self):  # تعديل السلوك (Overriding)
        print("Bark!")

dog = Dog()
dog.speak()  # Bark!
class Animal:
    def speak(self):
        return "Generic Sound"

class Dog(Animal):
    def speak(self):
        # يمكن استدعاء دالة الأب إذا لزم الأمر، أو تجاوزها تماماً
        return "Bark!"

def animal_sound(animal: Animal):
    print(animal.speak())

animal_sound(Dog())  # Bark!

💡 فوائد الوراثة

✅ إعادة استخدام الكود (لا حاجة لتكرار الطرق المشتركة)

✅ إنشاء علاقات هرمية واضحة

✅ سهولة التوسع والتعديل

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

يعني "نفس العملية، سلوك مختلف". يسمح لدوال بنفس الاسم بالعمل بطرق مختلفة بناءً على نوع الكائن.

class Cat:
    def speak(self):
        return "Meow"

class Dog:
    def speak(self):
        return "Bark"

animals = [Cat(), Dog()]
for animal in animals:
    print(animal.speak())
class Bird:
    def fly(self):
        print("Flying high")

class Airplane:
    def fly(self):
        print("Taking off")

# دالة تقبل أي كائن يمتلك دالة fly
def perform_flight(entity):
    if hasattr(entity, 'fly'):  # التحقق من وجود الدالة
        entity.fly()

perform_flight(Bird())      # Flying high
perform_flight(Airplane())  # Taking off
🦆 Duck Typing: في بايثون، إذا كان الكائن يمتلك الطريقة المطلوبة (مثل fly), سيتم تشغيلها بغض النظر عن نوع الفئة - "إذا كان يمشي مثل البطة ويصدر صوت البطة، فهو بطة!"

🔒 التغليف (Encapsulation)

تجميع البيانات والدوال داخل فئة، مع تقييد الوصول لبعض المكونات لحمايتها.

النوع الصيغة الوصول
Public self.name متاح للجميع
Protected self._name للفئة وفروعها
Private self.__name للفئة فقط
class Student:
    def __init__(self, age):
        self._age = age  # متغير محمي

    def get_age(self):
        return self._age

    def set_age(self, age):
        if age > 0:
            self._age = age
        else:
            print("Invalid age")

s = Student(20)
s.set_age(25)
print(s.get_age())
class Student:
    def __init__(self, age: int):
        self._age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value <= 0:
            raise ValueError("Age must be positive")
        self._age = value

s = Student(20)
s.age = 25  # يبدو كأنه متغير عادي، لكنه يستدعي الـ setter
print(s.age)

🎨 التجريد (Data Abstraction)

إخفاء تفاصيل التنفيذ المعقدة وإظهار الوظائف الضرورية فقط. يتم ذلك باستخدام الفئات المجردة (Abstract Classes) التي تحتوي على طرق مجردة يجب تنفيذها في الفئات الفرعية.

from abc import ABC, abstractmethod

class Shape(ABC):  # فئة مجردة
    @abstractmethod
    def area(self):
        pass

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):  # يجب تنفيذ الدالة هنا
        return self.side * self.side

sq = Square(4)
print(sq.area())  # 16
from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        """Subclasses must implement this method"""
        pass

class Lion(Animal):
    def sound(self):
        return "Roar"

# لا يمكن إنشاء كائن من Animal مباشرة لأنه مجرد
# a = Animal() # سيسبب خطأ
l = Lion()
print(l.sound())  # Roar

💡 فوائد التجريد

✅ إجبار الفئات الفرعية على تنفيذ الطرق المطلوبة

✅ إنشاء واجهات موحدة (Interfaces)

✅ التركيز على "ماذا يفعل" وليس "كيف يفعله"

Ad Space (Adaptive)