الفصل العاشر: البرمجة كائنية التوجه (OOP)
مقدمة الفصل
حتى الآن، تعلمنا تنظيم الكود في دوال لتجميع المهام المتكررة. هذا الأسلوب ممتاز، لكن عندما تكبر المشاريع وتصبح أكثر تعقيدًا، نحتاج إلى طريقة لتنظيم ليس فقط الأفعال (actions)، بل أيضًا البيانات (data) المرتبطة بتلك الأفعال.
هنا يأتي دور البرمجة كائنية التوجه (Object-Oriented Programming - OOP). إنها ليست مجرد مجموعة من الأوامر، بل هي طريقة تفكير ونمط لتصميم البرامج يحاكي العالم الحقيقي. في هذا النمط، نفكر في برنامجنا كمجموعة من “الكائنات” (objects) المستقلة التي تتفاعل مع بعضها البعض. كل كائن لديه خصائصه (بياناته) وسلوكياته (وظائفه).
1. الفكرة الأساسية: من البيانات والدوال إلى الكائنات
لنفكر في مثال من العالم الحقيقي: سيارة.
- خصائص السيارة (البيانات): لونها، موديلها، سرعتها الحالية، كمية الوقود.
- سلوكيات السيارة (الأفعال): يمكنها أن تتحرك، تتوقف، تسرّع، تبطئ.
في البرمجة التقليدية، قد نخزن هذه البيانات في متغيرات منفصلة، ونكتب دوال منفصلة للتعامل معها.
# الطريقة التقليدية
car_model = "Toyota"
car_color = "Red"
def move_car():
print("The car is moving...")
لكن لا يوجد رابط حقيقي بين البيانات والدوال.
في البرمجة كائنية التوجه، نقوم بتجميع كل ما يتعلق بالسيارة (بياناتها وسلوكياتها) داخل وحدة واحدة تسمى كائن (object).
2. المخطط الأساسي: الكلاس (Class)
قبل أن نبني سيارة حقيقية، نحتاج إلى مخطط أو تصميم (blueprint). في OOP، هذا المخطط هو الكلاس (Class).
الكلاس هو القالب الذي نستخدمه لإنشاء الكائنات. إنه يصف ما هي الخصائص والسلوكيات التي سيمتلكها أي كائن يتم إنشاؤه منه.
كيف نعرّف كلاسًا بسيطًا:
نستخدم الكلمة المفتاحية class
.
# Define a class named 'Car'
# تعريف كلاس اسمه 'Car'
class Car:
pass # 'pass' is a placeholder for future code
شرح الكود بالتفصيل:
class Car:
: هذا السطر يعلن عن إنشاء كلاس جديد اسمهCar
. جرت العادة أن تبدأ أسماء الكلاسات بحرف كبير.pass
: هي كلمة مفتاحية في بايثون لا تفعل شيئًا. نستخدمها هنا لأن بنية الكلاس لا يمكن أن تكون فارغة، وهي بمثابة “مؤقت” إلى أن نضيف الخصائص والدوال لاحقًا.
3. المُنشِئ: دالة __init__
عندما ننشئ كائنًا جديدًا من كلاس (أي عندما نبني سيارة حقيقية من المخطط)، غالبًا ما نرغب في تحديد خصائصه الأولية (مثل لونها وموديلها). هنا يأتي دور دالة خاصة جدًا اسمها __init__
.
تُسمى هذه الدالة بـ المُنشِئ (constructor)، ويتم استدعاؤها تلقائيًا في كل مرة يتم فيها إنشاء كائن جديد من الكلاس.
فهم self
والخصائص (Attributes):
self
: هو معامل إلزامي في كل دوال الكلاس، وهو يشير إلى الكائن نفسه الذي يتم إنشاؤه أو استخدامه. يمكنك التفكير فيه ككلمة “أنا” بالنسبة للكائن.- الخصائص (Attributes): هي المتغيرات التي تنتمي إلى الكائن، ونقوم بتعريفها داخل دالة
__init__
باستخدامself
.
class Car:
# The __init__ method is the constructor
# دالة __init__ هي المُنشئ
def __init__(self, brand, model, color):
# Attributes of the Car object
# خصائص كائن السيارة
self.brand = brand # 'brand' is an attribute of this car
self.model = model # 'model' is an attribute of this car
self.color = color # 'color' is an attribute of this car
self.is_running = False # A default attribute
# Now, we create an object (instance) of the Car class
# الآن، ننشئ كائنًا (نسخة) من كلاس السيارة
my_first_car = Car("Toyota", "Corolla", "Red")
# Access the attributes using dot notation
# الوصول إلى الخصائص باستخدام النقطة
print(f"My car brand is: {my_first_car.brand}")
print(f"Its color is: {my_first_car.color}")
شرح الكود بالتفصيل:
def __init__(self, brand, model, color):
: قمنا بتعريف المُنشئ. سيأخذbrand
وmodel
وcolor
كمدخلات عند إنشاء أي سيارة جديدة. المعاملself
يتم تمريره تلقائيًا بواسطة بايثون.self.brand = brand
: هذا السطر يقول: “بالنسبة لهذه السيارة (self
)، اجعل خاصيتها المسماةbrand
تساوي القيمة التي تم تمريرها في المعاملbrand
”.my_first_car = Car(...)
: هذا هو السطر الذي يتم فيه إنشاء الكائن. نحن نستدعي الكلاسCar
ونمرر له القيم (“Toyota”, “Corolla”, “Red”) كوسائط (arguments). تقوم بايثون باستدعاء دالة__init__
تلقائيًا وتمرير هذه القيم لها.my_first_car.brand
: بعد إنشاء الكائن، يمكننا الوصول إلى خصائصه باستخدام اسم الكائن متبوعًا بنقطة.
ثم اسم الخاصية.
4. السلوكيات: الدوال (Methods)
الدوال (Methods) هي ببساطة دوال يتم تعريفها داخل الكلاس. هي تمثل السلوكيات والأفعال التي يمكن للكائن القيام بها.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
self.is_running = False # The car is off by default
# A method to start the engine
# دالة لتشغيل المحرك
def start_engine(self):
if not self.is_running:
self.is_running = True
print(f"The {self.brand} {self.model}'s engine is now running.")
else:
print("The engine is already running.")
# A method to stop the engine
# دالة لإيقاف المحرك
def stop_engine(self):
if self.is_running:
self.is_running = False
print(f"The {self.brand} {self.model}'s engine has stopped.")
else:
print("The engine is already off.")
# Create an object
my_car = Car("Ford", "Mustang")
# Call the methods on the object
my_car.start_engine() # استدعاء دالة تشغيل المحرك
my_car.start_engine() # محاولة التشغيل مرة أخرى
my_car.stop_engine() # استدعاء دالة إيقاف المحرك
شرح الكود بالتفصيل:
def start_engine(self):
: قمنا بتعريف دالة جديدة داخل الكلاس. لاحظ أنها تأخذself
كمعامل أول، مما يسمح لها بالوصول إلى خصائص الكائن (مثلself.is_running
) وتعديلها.my_car.start_engine()
: نستدعي الدالة على الكائنmy_car
. عندما يتم هذا الاستدعاء، تقوم بايثون بتمريرmy_car
تلقائيًا كأول وسيط (مكانself
). هذا هو السبب في أن الدالةstart_engine
يمكنها معرفة ما إذا كان محرك هذه السيارة تحديدًا يعمل أم لا.
5. تمرين تطبيقي: تصميم حساب بنكي
المطلوب:
بناء كلاس يمثل حسابًا بنكيًا (BankAccount
). يجب أن يكون الكلاس قادرًا على تتبع اسم صاحب الحساب ورصيده، والسماح بعمليات الإيداع والسحب.
إرشادات الحل:
- تصميم الكلاس: أنشئ كلاسًا باسم
BankAccount
. - المُنشئ (
__init__
): يجب أن يأخذ المُنشئ معاملين عند إنشاء حساب جديد:owner_name
(اسم المالك) وinitial_balance
(الرصيد المبدئي). قم بتخزينهما كخصائص. - دالة الإيداع (
deposit
): أنشئ دالة باسمdeposit
تأخذ معاملًا واحدًا هوamount
(المبلغ). يجب أن تقوم هذه الدالة بإضافة المبلغ إلى الرصيد. - دالة السحب (
withdraw
): أنشئ دالة باسمwithdraw
تأخذ معاملًا واحدًا هوamount
. قبل السحب، يجب أن تتحقق الدالة مما إذا كان المبلغ المطلوب سحبه أقل من أو يساوي الرصيد المتاح.- إذا كان الرصيد كافيًا، قم بطرح المبلغ من الرصيد.
- إذا كان الرصيد غير كافٍ، اطبع رسالة خطأ ولا تقم بأي تغيير على الرصيد.
- دالة عرض الرصيد (
get_balance
): أنشئ دالة بسيطة تقوم فقط بطباعة الرصيد الحالي للحساب.
الحل المقترح خطوة بخطوة:
الخطوة 1: تعريف الكلاس والمُنشئ
# Define the BankAccount class
# تعريف كلاس الحساب البنكي
class BankAccount:
# The constructor to initialize the account
# المُنشئ لتهيئة الحساب
def __init__(self, owner_name, initial_balance=0):
self.owner = owner_name # Attribute for owner's name
self.balance = initial_balance # Attribute for the balance
print(f"Account for {self.owner} created with initial balance of {self.balance} DH.")
شرح الخطوة 1:
قمنا بتعريف الكلاس BankAccount
والمُنشئ __init__
. يأخذ المُنشئ اسم المالك ورصيدًا مبدئيًا (قيمته الافتراضية صفر إذا لم يتم تحديده). ثم يقوم بتخزين هذه القيم في خصائص الكائن: self.owner
و self.balance
.
الخطوة 2: إضافة دوال الإيداع والسحب وعرض الرصيد
class BankAccount:
# ... (the __init__ method from Step 1) ...
def __init__(self, owner_name, initial_balance=0):
self.owner = owner_name
self.balance = initial_balance
print(f"Account for {self.owner} created with initial balance of {self.balance} DH.")
# Method to deposit money
# دالة لإيداع المال
def deposit(self, amount):
if amount > 0:
self.balance += amount # Add the amount to the balance
print(f"Deposited {amount} DH. New balance is {self.balance} DH.")
else:
print("Deposit amount must be positive.")
# Method to withdraw money
# دالة لسحب المال
def withdraw(self, amount):
if amount > self.balance:
# Check for insufficient funds
# التحقق من عدم كفاية الرصيد
print("Withdrawal failed: Insufficient funds.")
elif amount <= 0:
print("Withdrawal amount must be positive.")
else:
self.balance -= amount # Subtract the amount from the balance
print(f"Withdrew {amount} DH. New balance is {self.balance} DH.")
# Method to display the balance
# دالة لعرض الرصيد
def get_balance(self):
print(f"The current balance for {self.owner} is {self.balance} DH.")
شرح الخطوة 2:
deposit
: تأخذ مبلغًا، تتحقق من أنه موجب، ثم تضيفه إلىself.balance
.withdraw
: تأخذ مبلغًا، ثم تقوم بسلسلة من التحققات: أولاً، هل الرصيد كافٍ؟ ثانيًا، هل المبلغ المطلوب سحبه موجب؟ إذا تم اجتياز التحققين، يتم خصم المبلغ من الرصيد.get_balance
: دالة بسيطة جدًا، وظيفتها فقط طباعة الرصيد الحالي.
الخطوة 3: استخدام الكلاس
# Create a new bank account object
# إنشاء كائن حساب بنكي جديد
account1 = BankAccount("Ahmed", 500)
print("\n--- Performing Transactions ---")
# Use the object's methods
# استخدام دوال الكائن
account1.get_balance() # Check initial balance
account1.deposit(200) # Deposit money
account1.withdraw(100) # Withdraw money
account1.withdraw(700) # Try to withdraw more than the balance
account1.get_balance() # Check final balance
شرح الخطوة 3:
هنا نرى قوة OOP في العمل. أنشأنا كائنًا account1
، ثم بدأنا بالتفاعل معه باستخدام الدوال التي صممناها. الكود منظم وسهل القراءة، حيث أن كل عملية (إيداع، سحب) هي استدعاء لدالة واضحة الاسم على الكائن الذي يخص “Ahmed”.
6. خلاصة الفصل
- البرمجة كائنية التوجه (OOP) هي نمط لتنظيم الكود عن طريق تجميع البيانات والسلوكيات معًا في “كائنات”.
- الكلاس (Class) هو المخطط أو القالب.
- الكائن (Object) هو نسخة حقيقية يتم إنشاؤها من الكلاس.
- دالة
__init__
هي المُنشئ الذي يهيئ خصائص الكائن عند إنشائه. - الخصائص (Attributes) هي بيانات الكائن (متغيرات
self
). - الدوال (Methods) هي أفعال الكائن (دوال
self
).
ماذا بعد؟
في الفصل الحادي عشر، سنعود لاستكشاف المزيد من الأدوات الجاهزة التي توفرها بايثون، وسنلقي نظرة على أهم الوحدات في مكتبة بايثون القياسية (Python Standard Library).