بايثون البرمجة كائنية التوجه - Python OOP

بايثون البرمجة كائنية التوجه - Python OOP

البرمجة كائنية التوجه، أو كما يحب كثير من المطورين تسميتها اختصارًا OOP، ليست مجرد طريقة لكتابة الكود بشكل “منظم” فحسب، بل هي طريقة تفكير كاملة تساعدك على بناء برامج قابلة للتوسع، سهلة الفهم، وأسهل في الصيانة عندما يكبر المشروع ويبدأ عدد الملفات والوظائف في الازدياد. وربما هذا هو السبب الحقيقي الذي يجعل كثيرًا من المبرمجين يشعرون أن الانتقال من كتابة دوال متفرقة إلى بناء كائنات Classes وObjects كان نقطة تحول في طريقة فهمهم للبرمجة. في البداية قد تبدو الفكرة ثقيلة قليلًا، خاصة إذا كنت قادمًا من عالم البرمجة الإجرائية، لكن مع الوقت ستكتشف أن البرمجة الكائنية ليست تعقيدًا إضافيًا، بل هي أداة لتبسيط التعقيد الحقيقي الموجود في المشاريع الكبيرة.

في هذا المقال الطويل سنأخذك في رحلة هادئة وعميقة داخل عالم Python OOP، من المفاهيم الأساسية إلى الأمثلة العملية، ومن الفكرة النظرية إلى التطبيق الواقعي. سنشرح الفئات، الكائنات، الخصائص، الدوال داخل الكائنات، الوراثة، تعدد الأشكال، التغليف، التجريد، ونتوقف أيضًا عند أفضل الممارسات والأخطاء الشائعة، ثم نختم بمشروع صغير يجمع كل ما تعلمته في مثال واحد متكامل. والأجمل من ذلك أننا سنفعل كل هذا بلغة عربية واضحة، وبأسلوب قريب من القارئ، حتى تشعر أن المفهوم لا يأتيك من كتاب جامد، بل من شخص يشرح لك بهدوء ويعطيك مساحة لتفهم وتجرّب.


ما هي البرمجة كائنية التوجه أصلًا؟

عندما نكتب برنامجًا، فنحن نحاول غالبًا تمثيل شيء من العالم الحقيقي أو من منطق العمل. قد نحتاج إلى تمثيل مستخدم، منتج، كتاب، سيارة، طالب، طلب شراء، أو حتى جلسة تسجيل دخول. في البرمجة التقليدية قد نقوم بكتابة الكثير من الدوال والمتغيرات بشكل منفصل، لكن في البرمجة كائنية التوجه نبدأ بالتفكير في “الكائن” نفسه: ما هي بياناته؟ ما هي سلوكياته؟ ما الذي يميّزه؟ كيف يتفاعل مع غيره؟

الكائن في البرمجة الكائنية يشبه شيئًا حيًا داخل البرنامج. له خصائص تصفه، وله سلوكيات يفعلها. فإذا أخذنا مثال “السيارة”، فإن خصائصها قد تكون اللون، الموديل، السرعة الحالية، وعدد الأبواب. أما سلوكياتها فقد تكون: تشغيل المحرك، التسارع، التوقف، أو تغيير السرعة. هذا التمثيل يجعل الكود أقرب إلى التفكير البشري، ولذلك يسهل فهمه في الفرق الكبيرة وبين المطورين المختلفين.

في Python، تعتبر البرمجة كائنية التوجه من أقوى الأساليب المستخدمة، لأن اللغة نفسها تدعمها بشكل ممتاز، وفي نفس الوقت لا تُجبرك على استخدامها في كل شيء. وهذا من جمال بايثون؛ فهي تمنحك الحرية لاختيار الأسلوب الأنسب. لكن عندما يصبح المشروع متوسطًا أو كبيرًا، أو عندما تحتاج إلى تنظيم واضح، فإن OOP تصبح أداة مهمة جدًا.


المفهوم الأساسي: Class و Object

قبل أن ندخل في التفاصيل، دعنا نرسخ الفرق بين class و object.

الـ class هو القالب أو المخطط.
أما الـ object فهو النسخة الفعلية المبنية من هذا القالب.

إذا أردنا تشبيهًا بسيطًا جدًا، فالـ class يشبه قالب الكعكة، بينما الـ object يشبه الكعكة التي خرجت من القالب. القالب واحد، لكن يمكنك أن تنتج منه عدة كائنات مختلفة في التفاصيل.

مثال بسيط جدًا

class Car:
    pass

my_car = Car()
your_car = Car()

print(my_car)
print(your_car)

في هذا المثال أنشأنا Class باسم Car، ثم أنشأنا منه كائنين my_car و your_car. رغم أن الكلاس فارغ الآن، إلا أنه يمثل البداية الصحيحة للفكرة. لاحظ أن my_car و your_car ليسا متطابقين تمامًا، بل هما كائنان مستقلان.

لماذا نحتاج هذا التقسيم؟

لأننا عندما نبني برنامجًا حقيقيًا، لا نريد أن تتناثر البيانات والمنطق في أماكن متفرقة. نريد أن نجمع ما يتعلق بشيء معين في مكان واحد. فمثلًا، إذا كان لديك نظام متجر إلكتروني، فمن الأفضل أن يكون لديك كلاس Product، وكلاس Customer، وكلاس Order، بدلًا من كتابة كل شيء في ملف واحد على شكل دوال متفرقة يصعب تتبعها. كلما أصبح المشروع أكبر، ظهرت قيمة هذا التنظيم بشكل أوضح.


أول كلاس في Python

لنبدأ بكلاس بسيط جدًا يمثل طالبًا.

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

student1 = Student("Ahmed", 20)
student2 = Student("Sara", 22)

print(student1.name)
print(student1.age)

print(student2.name)
print(student2.age)

ماذا يحدث هنا؟

الدالة __init__ تسمى constructor أو دالة التهيئة.
تُستدعى تلقائيًا عند إنشاء الكائن، وهدفها وضع القيم الأولية داخل الكائن.

  • self يشير إلى الكائن نفسه.

  • name و age هما القيم التي نمررها عند الإنشاء.

  • self.name = name تعني: خزّن الاسم داخل هذا الكائن.

  • self.age = age تعني: خزّن العمر داخل هذا الكائن.

قد يبدو الأمر بسيطًا جدًا، لكنه من أهم اللبنات في OOP. لأننا هنا لم نعد نتعامل مع متغيرات منفصلة، بل مع كائن كامل يحمل بياناته الخاصة.

جدول يوضح الفكرة

العنصر

المعنى

Class

قالب أو مخطط لإنشاء الكائنات

Object

نسخة فعلية من الكلاس

__init__

دالة تهيئة الكائن

self

مرجع إلى الكائن الحالي

Attribute

خاصية داخل الكائن

Method

دالة داخل الكائن


فهم self بطريقة مريحة

من أكثر الأشياء التي تربك المبتدئين في Python OOP هي كلمة self. الحقيقة أنها ليست شيئًا سحريًا، بل مجرد مرجع إلى الكائن الحالي. عندما تكتب داخل الكلاس self.name فأنت تعني “اسم هذا الكائن بالتحديد”.

دعنا نرى مثالًا أوضح:

class Person:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print(f"Hello, my name is {self.name}")

p1 = Person("Hassan")
p2 = Person("Mariam")

p1.say_hello()
p2.say_hello()

هنا كل كائن يملك اسمًا مختلفًا، ومع ذلك يستخدمان نفس الميثود say_hello. الفكرة جميلة جدًا: نفس السلوك، لكن البيانات تختلف من كائن لآخر.

لماذا نحتاج self؟

لأن Python يجب أن تعرف على أي كائن تعمل الدالة الآن.
بدون self لن يكون هناك فرق واضح بين بيانات كائن وآخر.


الخصائص Attributes والميثود Methods

في البرمجة الكائنية، أي كائن غالبًا يتكون من:

  1. خصائص: بيانات تصف الكائن.

  2. دوال/ميثود: أفعال أو سلوكيات يقوم بها الكائن.

لنأخذ مثالًا أكثر واقعية: حساب بنكي.

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        print(f"تم إيداع {amount}. الرصيد الحالي: {self.balance}")

    def withdraw(self, amount):
        if amount > self.balance:
            print("الرصيد غير كافٍ")
        else:
            self.balance -= amount
            print(f"تم سحب {amount}. الرصيد الحالي: {self.balance}")

account = BankAccount("Ali", 1000)

account.deposit(500)
account.withdraw(300)
account.withdraw(2000)

هنا لدينا:

  • owner و balance كخصائص.

  • deposit و withdraw كميثودات.

هذا المثال يوضح كيف نجمع البيانات والسلوك في مكان واحد. وهذا التجميع ليس مجرد ترتيب شكلي، بل هو جوهر OOP. لأنك عندما تعدل على الحساب، فأنت تتعامل مع الحساب نفسه بدل أن تبحث عن قيمة الرصيد في مكان آخر وتغيّرها يدويًا.

جدول مقارنة بين Attribute و Method

العنصر

النوع

مثال

name

Attribute

اسم الشخص

age

Attribute

العمر

deposit()

Method

إيداع مبلغ

withdraw()

Method

سحب مبلغ


لماذا البرمجة الكائنية مهمة في المشاريع الحقيقية؟

قد تقول: “أنا أستطيع كتابة البرنامج بدوال عادية، فلماذا أتعب نفسي بهذه المفاهيم؟” والسؤال منطقي جدًا. الجواب هو أن حجم المشروع يغيّر كل شيء. عندما يكون لديك 10 أسطر، فالأمر بسيط. لكن عندما يصبح لديك مئات أو آلاف الأسطر، أو فريق كامل يعمل معك، تظهر الحاجة إلى بنية واضحة. البرمجة الكائنية تساعدك على:

  • تنظيم الكود في وحدات مفهومة.

  • إعادة استخدام المنطق بدل تكراره.

  • تمثيل أشياء العالم الحقيقي بشكل طبيعي.

  • تسهيل الصيانة والتعديل لاحقًا.

  • تقليل الأخطاء الناتجة عن التداخل بين أجزاء البرنامج.

والأهم من كل هذا أنها تجعل طريقة التفكير نفسها أكثر نضجًا. بدل أن تسأل: “أين أضع هذه الدالة؟”، تبدأ تسأل: “إلى أي كائن تنتمي هذه المسؤولية؟”. وهذه النقلة في التفكير هي التي تصنع الفرق بين كود مؤقت وكود قابل للحياة.


التغليف Encapsulation: حماية البيانات وتنظيم الوصول إليها

التغليف يعني أن نحمي بيانات الكائن ونمنع التلاعب المباشر بها بشكل عشوائي. ليس المقصود أن نخفي كل شيء دائمًا، بل أن نتحكم في كيفية الوصول إلى البيانات بحيث لا يكتب أحد قيمة غير منطقية داخل الكائن.

في Python، لا يوجد private حقيقي كما في بعض اللغات الأخرى، لكن لدينا تقاليد واتفاقيات تساعدنا. على سبيل المثال، نضع underscore واحد _ أو اثنين __ لنعبر عن أن هذه الخاصية “داخلية” أو “لا يُفضل العبث بها مباشرة”.

مثال على التغليف

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

    def show_salary(self):
        print(f"Salary: {self.__salary}")

    def give_raise(self, amount):
        if amount > 0:
            self.__salary += amount
        else:
            print("Amount must be positive")

emp = Employee("Nora", 5000)
emp.show_salary()
emp.give_raise(1000)
emp.show_salary()

لو حاولت الوصول مباشرة إلى __salary من خارج الكلاس، فستجد أن Python تتعامل معها بشكل مختلف عبر ما يسمى name mangling. الفكرة ليست السرية المطلقة، بل الحماية العملية من الوصول العشوائي.

لماذا التغليف مفيد؟

لأنك لا تريد من أي جزء في البرنامج أن يغيّر الراتب أو الرصيد أو الحالة الداخلية بشكل غير متوقع. عندما تُجبر الآخرين على استخدام دوال محددة، أنت تضمن أن القواعد المنطقية تُطبق دائمًا.

مثال منطقي

إذا كان لديك حساب بنكي، فلا ينبغي أن يكتب أحد:

account.balance = -999999

بدلًا من ذلك، يجب أن يمر عبر طريقة سحب أو إيداع تنفذ القواعد اللازمة.


الوراثة Inheritance: إعادة استخدام الكود بذكاء

الوراثة من أشهر وأقوى المفاهيم في OOP. وهي تعني أن كلاسًا جديدًا يمكنه أن يرث من كلاس آخر. الكلاس الأب يحتوي على الخصائص والسلوكيات الأساسية، بينما الكلاس الابن يضيف عليها أو يخصصها.

مثال بسيط

class Animal:
    def eat(self):
        print("The animal is eating")

    def sleep(self):
        print("The animal is sleeping")

class Dog(Animal):
    def bark(self):
        print("The dog is barking")

dog = Dog()
dog.eat()
dog.sleep()
dog.bark()

هنا Dog يرث من Animal.
إذًا الكلب يستطيع الأكل والنوم لأنه ورث هذه السلوكيات، بالإضافة إلى أنه يملك سلوكًا خاصًا به: bark.

لماذا نستخدم الوراثة؟

لأننا لا نريد تكرار نفس الكود مرة بعد مرة. إذا كانت هناك خصائص عامة مشتركة بين عدة كائنات، نضعها في كلاس أب، ثم نخصص كل نوع في الكلاسات الفرعية.

جدول بسيط للوراثة

الكلاس الأب

ما يحتويه

Animal

الأكل، النوم

الكلاس الابن

ما يضيفه

Dog

النباح

Cat

المواء

Bird

الطيران


إعادة تعريف الميثود Override

أحيانًا يرث الكلاس الابن ميثود من الكلاس الأب، لكنه يحتاج أن يغيّر سلوكها. هنا نستخدم override.

class Animal:
    def sound(self):
        print("Some generic sound")

class Cat(Animal):
    def sound(self):
        print("Meow")

class Dog(Animal):
    def sound(self):
        print("Woof")

animals = [Cat(), Dog(), Animal()]

for animal in animals:
    animal.sound()

لاحظ هنا أن كل كائن يستخدم نفس اسم الدالة sound، لكن السلوك مختلف. هذا مثال جميل جدًا على تعدد الأشكال أيضًا، وسنعود إليه بعد قليل.


تعدد الأشكال Polymorphism: نفس الواجهة، سلوك مختلف

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

مثال عملي

class Bird:
    def move(self):
        print("Flying")

class Fish:
    def move(self):
        print("Swimming")

class Human:
    def move(self):
        print("Walking")

for creature in [Bird(), Fish(), Human()]:
    creature.move()

هنا كل كائن يملك move()، لكن التنفيذ مختلف.
هذا هو جوهر تعدد الأشكال: نفس الاسم، لكن النتيجة تتكيف مع الكائن.

لماذا هذه الفكرة جميلة؟

لأنها تجعل الكود أكثر مرونة. يمكنك أن تمرر كائنات مختلفة إلى نفس الدالة، طالما أنها تملك نفس السلوك المتوقع. وهذه من أجمل نقاط القوة في Python، لأنها تشجعك على البرمجة المرنة والبسيطة بدل التعقيد الزائد.


التجريد Abstraction: التركيز على المهم وإخفاء التفاصيل

التجريد يعني أن نعرض للمستخدم ما يحتاجه فقط، ونخفي التفاصيل المعقدة داخليًا. عندما تقود سيارة، أنت لا تحتاج أن تعرف كل ما يحدث داخل المحرك على مستوى الاحتراق والضغط والدوائر الكهربائية. أنت فقط تستخدم المقود والفرامل والدواسة. البرمجة تفعل شيئًا مشابهًا.

في Python، يمكن تطبيق التجريد عبر ABC من مكتبة abc.

مثال على التجريد

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

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

rect = Rectangle(10, 5)
circle = Circle(7)

print(rect.area())
print(circle.area())

هنا Shape كلاس مجرد، لا نستخدمه مباشرة، بل نستخدمه كفكرة عامة. أي كلاس يرث منه يجب أن يحقق الدالة area.

لماذا نحتاج التجريد؟

لأنه يفرض قواعد معينة على بنيتك البرمجية.
وبدل أن تترك كل مطور يكتب ما يشاء، تحدد له شكلًا واضحًا: “أي شكل يجب أن يحسب المساحة”.


الفرق بين الوراثة والتجريد

هذا سؤال شائع جدًا، وكثير من المبتدئين يخلطون بينهما.
الوراثة تعني: “هذا الكلاس يستفيد من كلاس آخر”.
أما التجريد فيعني: “هناك شكل عام يجب أن تُنفذه الكلاسات المختلفة”.

جدول يوضح الفرق

المفهوم

الفكرة

الاستخدام

Inheritance

إعادة استخدام سلوك كلاس آخر

عندما توجد علاقة “is-a”

Abstraction

فرض واجهة موحدة

عندما تريد إلزام الكلاسات بتنفيذ سلوك معين

مثال Dog is an Animal هو وراثة.
لكن Shape كفكرة يجب أن تمتلك area() هو تجريد.


التركيب Composition: بديل ذكي للوراثة في كثير من الحالات

كثير من المطورين الجدد يتجهون إلى الوراثة بسرعة، لكن في الواقع ليست الوراثة دائمًا الحل الأفضل. أحيانًا يكون التركيب Composition أفضل بكثير. التركيب يعني أن كائنًا يحتوي على كائن آخر بداخله بدل أن يرث منه.

مثال

class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()

    def start_car(self):
        self.engine.start()
        print("Car is moving")

car = Car()
car.start_car()

هنا السيارة لا “ترث” من المحرك، بل “تحتويه”. هذه علاقة منطقية أكثر في كثير من الحالات. السيارة ليست نوعًا من المحرك، لكنها تحتوي على محرك. وهذا هو الفرق الأساسي.

لماذا التركيب مهم؟

لأنه يجعل التصميم أكثر مرونة. عندما تربط الكائنات بشكل مناسب، يصبح تعديلها لاحقًا أسهل. أحيانًا الوراثة تخلق علاقات معقدة وصعبة الصيانة، بينما التركيب يبقي الأمور أوضح.


دوال __str__ و __repr__: جعل الكائنات أكثر قابلية للقراءة

عند طباعة كائن في Python، قد يظهر لك شيء غير مفيد مثل عنوان الذاكرة. لذلك نستعمل __str__ أو __repr__ لتحديد كيف يظهر الكائن عند طباعته.

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"Book: {self.title} by {self.author}"

book = Book("Atomic Habits", "James Clear")
print(book)

النتيجة ستكون نصًا جميلًا ومفهومًا بدل تمثيل تقني غامض.
هذا مهم جدًا في التطبيقات الحقيقية، خاصة عند تسجيل البيانات أو عرضها في الواجهة أو أثناء debugging.

الفرق بين __str__ و __repr__

  • __str__ مخصص للعرض الودي للمستخدم.

  • __repr__ مخصص للتمثيل الدقيق للمطور، وغالبًا يكون أكثر تفصيلًا.

مثال:

class User:
    def __init__(self, username):
        self.username = username

    def __repr__(self):
        return f"User(username={self.username!r})"

الميثودات الساكنة والميثودات الصفية

في Python، ليست كل الدوال داخل الكلاس مرتبطة بالكائن نفسه. أحيانًا نحتاج دالة تنتمي للكلاس لكن لا تعتمد على self. هنا نستعمل @staticmethod. وأحيانًا نحتاج دالة تتعامل مع الكلاس نفسه بدل الكائن، فنستخدم @classmethod.

@staticmethod

class MathTools:
    @staticmethod
    def add(a, b):
        return a + b

print(MathTools.add(5, 7))

هذه الدالة لا تحتاج إلى self ولا إلى cls.
هي فقط وظيفة موجودة داخل الكلاس لأسباب تنظيمية.

@classmethod

class Product:
    discount = 0.1

    def __init__(self, price):
        self.price = price

    @classmethod
    def set_discount(cls, value):
        cls.discount = value

    def final_price(self):
        return self.price - (self.price * self.discount)

Product.set_discount(0.2)
p = Product(100)
print(p.final_price())

هنا cls تشير إلى الكلاس نفسه، وليس إلى الكائن.

جدول الاختلافات

النوع

يأخذ self

يأخذ cls

يستخدم لما؟

Instance Method

نعم

لا

عندما يعمل على بيانات الكائن

Class Method

لا

نعم

عندما يعمل على بيانات الكلاس

Static Method

لا

لا

عندما تكون الدالة مرتبطة منطقيًا بالكلاس لكنها لا تحتاج بياناته


الخواص Properties: التحكم الذكي في القراءة والكتابة

أحيانًا نريد أن تبدو الخاصية وكأنها متغير عادي، لكن فعليًا نريد التحكم في كيفية قراءتها أو تعديلها. هنا تأتي @property.

مثال

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature cannot be below absolute zero")
        self._celsius = value

temp = Temperature(25)
print(temp.celsius)

temp.celsius = 30
print(temp.celsius)

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


الكائنات القابلة للتكرار Iteration وطرق خاصة أخرى

Python مليئة بما يسمى dunder methods أو magic methods، وهي دوال تبدأ وتنتهي بشرطتين سفليتين. هذه الدوال تسمح لك بإعطاء الكائنات سلوكًا مخصصًا يشبه السلوك الافتراضي في اللغة.

مثال على الجمع أو المقارنة

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 __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(2, 3)
v2 = Vector(4, 5)

print(v1 + v2)

الآن الكائنات الخاصة بك بدأت تتصرف بشكل طبيعي جدًا داخل Python.
هذه من أجمل ميزات OOP في اللغة: أنك لا تكتب كائنات “جامدة”، بل كائنات تتكلم لغة Python نفسها.


مثال عملي شامل: نظام مكتبة بسيط

الآن دعنا نجمع بعض المفاهيم معًا في مثال واحد أقرب إلى الواقع. سنبني نظامًا بسيطًا لإدارة مكتبة.

الفكرة

لدينا:

  • كتاب.

  • عضو.

  • مكتبة.

الكتاب له عنوان ومؤلف وتوفر.
العضو يستطيع استعارة كتاب وإرجاعه.
المكتبة تدير المجموعة الكاملة من الكتب.

الكود

class Book:
    def __init__(self, title, author, is_available=True):
        self.title = title
        self.author = author
        self.is_available = is_available

    def borrow(self):
        if self.is_available:
            self.is_available = False
            print(f"تم استعارة الكتاب: {self.title}")
        else:
            print(f"الكتاب {self.title} غير متاح الآن")

    def return_book(self):
        self.is_available = True
        print(f"تم إرجاع الكتاب: {self.title}")

    def __str__(self):
        status = "متاح" if self.is_available else "معار"
        return f"{self.title} - {self.author} - {status}"


class Member:
    def __init__(self, name):
        self.name = name
        self.borrowed_books = []

    def borrow_book(self, book):
        if book.is_available:
            book.borrow()
            self.borrowed_books.append(book)
            print(f"{self.name} استعار الكتاب {book.title}")
        else:
            print(f"عذرًا، الكتاب {book.title} غير متاح")

    def return_book(self, book):
        if book in self.borrowed_books:
            book.return_book()
            self.borrowed_books.remove(book)
            print(f"{self.name} أعاد الكتاب {book.title}")
        else:
            print(f"{self.name} لم يستعر هذا الكتاب")


class Library:
    def __init__(self):
        self.books = []
        self.members = []

    def add_book(self, book):
        self.books.append(book)

    def add_member(self, member):
        self.members.append(member)

    def show_books(self):
        for book in self.books:
            print(book)

    def show_members(self):
        for member in self.members:
            print(member.name)


library = Library()

book1 = Book("Clean Code", "Robert C. Martin")
book2 = Book("Python Crash Course", "Eric Matthes")
book3 = Book("Automate the Boring Stuff", "Al Sweigart")

member1 = Member("Hassan")
member2 = Member("Sara")

library.add_book(book1)
library.add_book(book2)
library.add_book(book3)

library.add_member(member1)
library.add_member(member2)

library.show_books()
print("----")

member1.borrow_book(book1)
member2.borrow_book(book1)
member2.borrow_book(book2)

print("----")
library.show_books()

print("----")
member1.return_book(book1)

print("----")
library.show_books()

ماذا تعلمنا من هذا المثال؟

  1. كل كلاس عنده مسؤولية واضحة.

  2. البيانات ليست مبعثرة.

  3. السلوك قريب من الواقع.

  4. يمكن تطوير النظام بسهولة لاحقًا.

وهذا بالضبط هو ما نبحث عنه في OOP: وضوح + تنظيم + قابلية التوسع.


مثال آخر: إدارة موظفين ورواتب

لنأخذ مثالًا مختلفًا يوضح الوراثة والتجريد.

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def calculate_salary(self):
        pass


class FullTimeEmployee(Employee):
    def __init__(self, name, monthly_salary):
        super().__init__(name)
        self.monthly_salary = monthly_salary

    def calculate_salary(self):
        return self.monthly_salary


class Freelancer(Employee):
    def __init__(self, name, hourly_rate, hours_worked):
        super().__init__(name)
        self.hourly_rate = hourly_rate
        self.hours_worked = hours_worked

    def calculate_salary(self):
        return self.hourly_rate * self.hours_worked


employees = [
    FullTimeEmployee("Omar", 12000),
    Freelancer("Lina", 100, 80)
]

for employee in employees:
    print(employee.name, employee.calculate_salary())

هنا نرى قوة التصميم الجيد.
لدينا قاعدة عامة اسمها Employee، لكن طرق حساب الراتب تختلف حسب النوع. ومع ذلك نستطيع التعامل مع جميع الموظفين بنفس الواجهة calculate_salary().


متى أستخدم OOP ومتى أكتفي بالدوال؟

هذا سؤال مهم جدًا.
ليس من الحكمة أن تحوّل كل شيء إلى كائنات بشكل مبالغ فيه. أحيانًا يكون الحل الأبسط هو الأفضل.

استخدم OOP عندما:

  • يكون لديك كيان واضح له بيانات وسلوك.

  • المشروع كبير أو قابل للتوسع.

  • تحتاج إلى تنظيم منظم وسهل الصيانة.

  • يوجد أكثر من نوع يشترك في نفس الفكرة.

  • تريد تمثيل الواقع بشكل منطقي.

استخدم الدوال البسيطة عندما:

  • المهمة صغيرة ومحددة.

  • لا توجد حاجة لتخزين حالة state داخل كائن.

  • الحل الإجرائي أوضح وأخف.

  • تريد تنفيذ عمليات مستقلة لا تعتمد على بيانات داخلية.

جدول قرار سريع

الحالة

الأنسب

حساب بسيط لمعادلة

دالة

إدارة منتجات متجر

OOP

تحويل نص إلى حروف كبيرة

دالة

نظام مستخدمين وصلاحيات

OOP

قراءة ملف صغير وتعديله

حسب التعقيد


أخطاء شائعة يقع فيها المبتدئون

1) الإفراط في استخدام الوراثة

كثيرون يظنون أن كل شيء يجب أن يرث من شيء آخر. هذا غير صحيح.
أحيانًا يكون التركيب أفضل من الوراثة.

2) نسيان استخدام self

من السهل أن تكتب متغيرًا داخل الكلاس وتنسى أنه يجب أن يكون self.x.
عندها يصبح المتغير محليًا للدالة وليس خاصية للكائن.

3) وضع منطق كثير داخل __init__

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

4) تعقيد التصميم بلا داعٍ

OOP ليست عرضًا للاستعراض.
لا تحوّل مشروعًا بسيطًا إلى هيكل ضخم من الكلاسات لمجرد أنك تستطيع.

5) الوصول المباشر إلى البيانات الداخلية

استخدم الخصائص والميثودات عندما تحتاج إلى قواعد تحكم، بدل العبث المباشر بالقيم.


أفضل الممارسات في Python OOP

اجعل كل كلاس مسؤولًا عن شيء واضح

الكلاس الجيد هو الذي يعرف ماذا يفعل، وماذا لا يفعل.
كلما زادت مسؤوليات الكلاس، زاد صعوبة فهمه.

اختر أسماء مفهومة

الاسم الجيد نصف الفهم.
استخدم أسماء مثل User, Order, Invoice, Payment, Library, بدل أسماء مبهمة.

ابدأ ببساطة

لا تحاول بناء تصميم مثالي من أول مرة.
ابدأ بشكل واضح، ثم طوّره عندما تظهر الحاجة.

استخدم composition عندما يكون أنسب

لا تتسرع في الوراثة.
اسأل نفسك: هل العلاقة حقًا “is-a”، أم أنها “has-a”؟

اكتب كودًا قابلًا للقراءة

الكود يُقرأ أكثر مما يُكتب.
وهذه حقيقة لا يلمسها المبتدئ فورًا، لكنها تصبح واضحة جدًا مع الوقت.


مقارنة بين البرمجة الإجرائية والبرمجة الكائنية

الجانب

البرمجة الإجرائية

البرمجة الكائنية

التنظيم

دوال ومتغيرات منفصلة

كائنات تجمع البيانات والسلوك

الصيانة

أصعب مع المشاريع الكبيرة

أسهل نسبيًا

إعادة الاستخدام

موجودة لكن محدودة

أقوى وأكثر تنظيمًا

تمثيل الواقع

أقل مباشرة

أقرب للواقع

التوسع

قد يصبح معقدًا

أوضح وأكثر قابلية للنمو

هذه المقارنة لا تعني أن واحدة أفضل مطلقًا من الأخرى.
بل تعني أن لكل أسلوب مكانه. الأذكى هو أن تعرف متى تستخدم كل أسلوب.


Mini Project: نظام متجر بسيط

لنختم بمشروع صغير يوضح كيف يمكن لـ OOP أن تساعدك في بناء نظام منطقي.

المطلوب

لدينا منتجات، سلة شراء، وطلب.

الكود

class Product:
    def __init__(self, name, price, quantity=1):
        self.name = name
        self.price = price
        self.quantity = quantity

    def total_price(self):
        return self.price * self.quantity

    def __str__(self):
        return f"{self.name} x{self.quantity} = {self.total_price()}"


class Cart:
    def __init__(self):
        self.items = []

    def add_product(self, product):
        self.items.append(product)

    def remove_product(self, product_name):
        self.items = [item for item in self.items if item.name != product_name]

    def cart_total(self):
        return sum(item.total_price() for item in self.items)

    def show_cart(self):
        for item in self.items:
            print(item)
        print(f"Total: {self.cart_total()}")


class Order:
    def __init__(self, cart, customer_name):
        self.cart = cart
        self.customer_name = customer_name
        self.status = "Pending"

    def confirm(self):
        if self.cart.cart_total() > 0:
            self.status = "Confirmed"
            print(f"Order confirmed for {self.customer_name}")
        else:
            print("Cart is empty")

    def show_order(self):
        print(f"Customer: {self.customer_name}")
        self.cart.show_cart()
        print(f"Status: {self.status}")


cart = Cart()
cart.add_product(Product("Laptop", 12000, 1))
cart.add_product(Product("Mouse", 250, 2))
cart.add_product(Product("Keyboard", 400, 1))

order = Order(cart, "Hassan")
order.show_order()
print("----")
order.confirm()
order.show_order()

ما الذي يبرهنه هذا المثال؟

  • المنتج هو كائن مستقل.

  • السلة كائن يدير المنتجات.

  • الطلب كائن أعلى يتعامل مع السلة واسم العميل.

  • كل شيء منظم بشكل منطقي جدًا.

هذا النوع من البناء هو ما ستراه كثيرًا في التطبيقات الحقيقية، حتى لو كانت التفاصيل أكثر تعقيدًا.


جدول يلخص مفاهيم Python OOP الأساسية

المفهوم

المعنى المختصر

مثال

Class

قالب لإنشاء الكائنات

class Car:

Object

نسخة من الكلاس

my_car = Car()

Attribute

خاصية داخل الكائن

self.name

Method

دالة داخل الكائن

def drive(self):

Constructor

تهيئة الكائن

__init__

Encapsulation

حماية وتنظيم البيانات

_salary أو __salary

Inheritance

كلاس يرث من كلاس آخر

class Dog(Animal):

Polymorphism

نفس الواجهة، سلوك مختلف

sound()

Abstraction

إخفاء التفاصيل وإظهار المهم

ABC

Composition

كائن يحتوي كائنًا آخر

Car يحتوي Engine


كيف تتعلم OOP بشكل فعلي لا نظري فقط؟

الفرق بين من “يفهم” OOP على الورق ومن “يستخدمها” فعليًا في المشاريع كبير جدًا. المعرفة الحقيقية تأتي عندما تبدأ في بناء أشياء صغيرة بنفسك. لا تكتفِ بقراءة المفاهيم. جرب أن تبني:

  • نظام طلاب.

  • متجر صغير.

  • بنك بسيط.

  • مكتبة.

  • إدارة مهام To-Do.

  • نظام موظفين ورواتب.

كل مشروع صغير سيكشف لك زاوية جديدة. وأحيانًا، أخطاءك في البداية ستكون أفضل معلم لك. ستكتب تصميمًا ثم تكتشف أنه معقد أكثر من اللازم. ستعيد البناء بشكل أبسط. ستلاحظ أن بعض الكلاسات يجب أن تنقسم إلى كائنات أصغر. وهذه التجربة المتكررة هي التي تصنع حسّ التصميم لديك.


نصائح عملية للمبتدئ العربي الذي يتعلم Python OOP

إذا كنت تتعلم الآن، فلا تتوقع أن تفهم كل شيء من أول مرة، وهذا أمر طبيعي جدًا. OOP تحتاج إلى وقت حتى تستقر في الذهن، خاصة إذا كنت جديدًا على البرمجة. الأفضل أن تبدأ بهذه الطريقة:

  1. افهم class و object أولًا.

  2. جرّب __init__ و self.

  3. أضف methods بسيطة.

  4. انتقل إلى الوراثة.

  5. بعد ذلك تعلّم @property و @classmethod.

  6. ثم جرّب التجريد والتصميم العملي.

وأهم شيء: لا تجعل اللغة الإنجليزية حاجزًا يمنعك من الفهم. كثير من المفاهيم لها أسماء أجنبية، لكن معناها الحقيقي بسيط جدًا إذا ربطته بأمثلة من الحياة اليومية. حين تقول “كائن”، تخيل شيئًا حقيقيًا. حين تقول “خاصية”، تخيل صفة. حين تقول “سلوك”، تخيل فعلًا. هكذا يصبح الموضوع أقرب إلى العقل والقلب معًا.


خاتمة: لماذا تستحق OOP كل هذا الاهتمام؟

البرمجة كائنية التوجه ليست مجرد باب من أبواب Python، بل هي طريقة لترتيب الأفكار داخل الكود. ربما تبدأ بها لأنك تريد أن تتعلم لغة البرمجة بشكل جيد، لكنك ستبقى معها لأنها تمنحك شعورًا بالسيطرة على المشروع. بدل أن تتعامل مع البرنامج كفوضى من الدوال، تبدأ في رؤيته كعالم صغير من الكائنات التي تتفاعل معًا. وكلما ازداد فهمك لهذه الفلسفة، أصبح كودك أوضح، وأقل ارتباكًا، وأكثر احترامًا لنفسه ولمن سيقرأه بعدك.

الجميل في Python أنها لا تفرض عليك التعقيد. تستطيع أن تبدأ ببساطة، ثم تتطور تدريجيًا. ولهذا فإن تعلم Python OOP ليس فقط خطوة تقنية، بل هو أيضًا تدريب على التفكير المنظم. وعندما تلتقط هذه الفكرة جيدًا، ستجد نفسك تنظر إلى المشاريع بطريقة مختلفة: ماذا يمثل هذا الجزء؟ هل هو كلاس؟ هل هو سلوك؟ هل هو مجرد دالة؟ هل أحتاج وراثة أم تركيبًا؟ وهذه الأسئلة هي التي تصنع المطور الناضج.

إذا نظرت إلى كل ما سبق، ستجد أن OOP ليست مادة للحفظ، بل أسلوب لفهم العالم داخل البرمجة. وكلما مارستها أكثر، أصبحت أقرب إلى طبيعتك في التفكير.


تمارين مقترحة للتطبيق

يمكنك أن تتدرب على هذه الأفكار بنفسك عبر تنفيذ المشاريع التالية:

التمرين

الفكرة

Student Manager

إدارة الطلاب والدرجات

Bank System

حسابات، إيداع، سحب

Library System

كتب، أعضاء، استعارة

Store Cart

منتجات وسلة وطلب

Employee Payroll

موظفون وأنواع رواتب


خريطة ذهنية مختصرة لما تعلمته

المستوى

ما يجب أن تتقنه

البداية

class, object, self, init

المتوسط

methods, attributes, encapsulation

الجيد

inheritance, overriding, polymorphism

المتقدم

abstraction, composition, properties, dunder methods

الاحترافي

design thinking, separation of concerns, clean code


كلمة أخيرة

إذا شعرت يومًا أن OOP كثيرة التفاصيل، فتذكر أنها في الأصل محاولة لجعل الفوضى أقل. كل مفهوم فيها جاء ليحل مشكلة حقيقية: التكرار، ضعف التنظيم، صعوبة الصيانة، وتضخم الكود. لهذا السبب، لا تتعامل معها كمجموعة مصطلحات، بل كأدوات تبني بها برامج أنظف وأذكى. ومع كل كلاس جديد ستكتبه، ستشعر أن البرمجة لم تعد مجرد أوامر تنفذها الآلة، بل لغة تنظم بها العالم الذي تحاول بناءه.

#Python OOP #Python Object Oriented Programming #البرمجة كائنية التوجه #Python Classes #Python Objects #تعلم Python OOP #شرح OOP بايثون