التغليف (Encapsulation) في Python
تخيل أنك تبني خزنة لحفظ الأموال. لا تريد أن يفتحها أي شخص مباشرة، بل تريد التحكم في من يدخل وكيف يدخل. هذا بالضبط ما يفعله التغليف (Encapsulation)! إنه مبدأ أساسي في تعلم البرمجة بالعربي يسمح لنا بإخفاء البيانات الحساسة داخل الكائنات وحمايتها من التعديل العشوائي.
1. تعريف التغليف (What is Encapsulation?)
التغليف هو آلية إخفاء البيانات الداخلية للكائن ومنع الوصول المباشر إليها من خارج الصنف. بدلاً من ذلك، نوفر طرقاً محددة (Methods) للتعامل مع هذه البيانات بشكل آمن ومنظم.
في دروس برمجة عربية احترافية، نتعلم أن التغليف يحقق هدفين رئيسيين:
- الحماية (Protection): منع تعديل البيانات بطرق خاطئة قد تسبب أخطاء.
- التحكم (Control): إجبار المستخدمين على استخدام طرق محددة للوصول إلى البيانات.
مثال من حياتنا
عندما تستخدم جهاز الصراف الآلي (ATM)، لا تستطيع الوصول مباشرة إلى قاعدة البيانات لتغيير رصيدك! بدلاً من ذلك، تستخدم واجهة محددة (الشاشة والأزرار) التي تتحكم في كيفية التعامل مع بياناتك.
هكذا يعمل التغليف: البيانات محمية، والوصول إليها يتم عبر طرق آمنة فقط.
2. مستويات الوصول في Python
في بايثون، نستخدم تسميات خاصة للمتغيرات لتحديد مستوى الوصول إليها:
أ- الخصائص العامة (Public)
يمكن الوصول إليها من أي مكان. هذا هو السلوك الافتراضي.
class Person:
def __init__(self, name):
self.name = name # خاصية عامة
person = Person("أحمد")
print(person.name) # يمكن الوصول مباشرة
ب- الخصائص المحمية (Protected)
نستخدم شرطة سفلية واحدة _ قبل الاسم. هذا اتفاق بين المبرمجين يعني "لا تستخدم هذا خارج الصنف".
class Person:
def __init__(self, age):
self._age = age # خاصية محمية (اتفاقية)
ج- الخصائص الخاصة (Private)
نستخدم شرطتين سفليتين __ قبل الاسم. بايثون تقوم بـ "Name Mangling" لجعل الوصول صعباً من الخارج.
class Person:
def __init__(self, salary):
self.__salary = salary # خاصية خاصة (مخفية)
3. أمثلة تطبيقية
المثال الأول: التغليف الأساسي
لنبدأ بمثال بسيط يوضح الفرق بين الخصائص العامة والخاصة.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # عام: يمكن الوصول
self.__balance = balance # خاص: مخفي
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"تم إيداع {amount} ريال")
else:
print("المبلغ يجب أن يكون موجباً!")
# إنشاء حساب
account = BankAccount("محمد", 1000)
# الوصول للخاصية العامة
print(f"صاحب الحساب: {account.owner}")
# الوصول للخاصية الخاصة عبر دالة
print(f"الرصيد: {account.get_balance()}")
# محاولة الوصول المباشر للرصيد (خطأ!)
# print(account.__balance) # AttributeError
# الطريقة الصحيحة: استخدام الدالة
account.deposit(500)
شرح الكود سطراً بسطر:
class BankAccount:- نعرّف صنف الحساب البنكي.self.owner = owner- خاصية عامة يمكن الوصول إليها مباشرة.self.__balance = balance- خاصية خاصة (لاحظ الشرطتين __) لحماية الرصيد.def get_balance(self):- دالة للوصول الآمن للرصيد (Getter).def deposit(self, amount):- دالة للإيداع مع التحقق من صحة المبلغ.if amount > 0:- نتحقق أن المبلغ موجب قبل الإيداع.print(account.owner)- نصل للخاصية العامة مباشرة (مسموح).account.get_balance()- نستخدم الدالة للوصول للرصيد (الطريقة الصحيحة).
الفائدة: الرصيد محمي ولا يمكن تعديله إلا عبر دوال محددة تتحقق من صحة العملية!
المثال الثاني: Getters و Setters
في شرح البرمجة باللغة العربية، نتعلم استخدام دوال خاصة للقراءة (Getter) والكتابة (Setter) مع التحقق من البيانات.
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
# Getter للاسم
def get_name(self):
return self.__name
# Setter للاسم مع التحقق
def set_name(self, name):
if len(name) > 0:
self.__name = name
else:
print("الاسم لا يمكن أن يكون فارغاً!")
# Getter للعمر
def get_age(self):
return self.__age
# Setter للعمر مع التحقق
def set_age(self, age):
if 5 <= age <= 100:
self.__age = age
else:
print("العمر غير منطقي!")
# إنشاء طالب
student = Student("سارة", 20)
# قراءة البيانات
print(f"الاسم: {student.get_name()}")
print(f"العمر: {student.get_age()}")
# تعديل صحيح
student.set_age(21)
print(f"العمر الجديد: {student.get_age()}")
# محاولة تعديل خاطئ
student.set_age(150) # سيرفض التعديل
student.set_name("") # سيرفض التعديل
شرح الكود سطراً بسطر:
self.__name = name- نخزن الاسم كخاصية خاصة.self.__age = age- نخزن العمر كخاصية خاصة.def get_name(self):- Getter لقراءة الاسم.def set_name(self, name):- Setter لتعديل الاسم مع التحقق.if len(name) > 0:- نتحقق أن الاسم ليس فارغاً.if 5 <= age <= 100:- نتحقق أن العمر في نطاق منطقي.student.set_age(150)- محاولة تعديل خاطئ سيتم رفضها.
الفكرة: نتحكم في كيفية تعديل البيانات ونمنع القيم الخاطئة!
المثال الثالث: استخدام @property (الطريقة الاحترافية)
بايثون توفر طريقة أنيقة لإنشاء Getters و Setters باستخدام @property.
class Product:
def __init__(self, name, price):
self.__name = name
self.__price = price
# Getter باستخدام @property
@property
def price(self):
return self.__price
# Setter باستخدام @price.setter
@price.setter
def price(self, value):
if value >= 0:
self.__price = value
else:
print("السعر لا يمكن أن يكون سالباً!")
@property
def name(self):
return self.__name
# إنشاء منتج
product = Product("لابتوب", 3000)
# قراءة السعر (كأنه خاصية عادية!)
print(f"المنتج: {product.name}")
print(f"السعر: {product.price} ريال")
# تعديل السعر (كأنه خاصية عادية!)
product.price = 2500
print(f"السعر الجديد: {product.price} ريال")
# محاولة تعديل خاطئ
product.price = -100
شرح الكود سطراً بسطر:
self.__price = price- نخزن السعر كخاصية خاصة.@property- Decorator يحول الدالة إلى Getter.def price(self):- دالة تُستدعى عند قراءة price.@price.setter- Decorator يحول الدالة إلى Setter.def price(self, value):- دالة تُستدعى عند تعديل price.product.price = 2500- نعدل السعر كأنه خاصية عادية، لكن Setter يعمل في الخلفية!
الميزة: الكود يبدو بسيطاً وطبيعياً، لكن الحماية موجودة في الخلفية!
المثال الرابع: تطبيق واقعي - نظام المستخدمين
مثال عملي يجمع كل ما تعلمناه في نظام إدارة مستخدمين.
class User:
def __init__(self, username, password):
self.__username = username
self.__password = password
self.__is_logged_in = False
@property
def username(self):
return self.__username
def login(self, password):
if password == self.__password:
self.__is_logged_in = True
print(f"مرحباً {self.__username}! تم تسجيل الدخول بنجاح")
return True
else:
print("كلمة المرور خاطئة!")
return False
def logout(self):
self.__is_logged_in = False
print("تم تسجيل الخروج")
def is_logged_in(self):
return self.__is_logged_in
def change_password(self, old_password, new_password):
if old_password == self.__password:
if len(new_password) >= 6:
self.__password = new_password
print("تم تغيير كلمة المرور بنجاح!")
else:
print("كلمة المرور الجديدة قصيرة جداً!")
else:
print("كلمة المرور القديمة خاطئة!")
# إنشاء مستخدم
user = User("ahmed", "pass123")
# محاولة الوصول لكلمة المرور مباشرة (محمية!)
# print(user.__password) # خطأ!
# تسجيل الدخول
user.login("wrong") # فشل
user.login("pass123") # نجح
# تغيير كلمة المرور
user.change_password("pass123", "new") # قصيرة
user.change_password("pass123", "newpass456") # نجح
# تسجيل الخروج
user.logout()
شرح الكود سطراً بسطر:
self.__password = password- كلمة المرور مخفية تماماً ولا يمكن الوصول إليها.self.__is_logged_in = False- حالة تسجيل الدخول محمية.def login(self, password):- الطريقة الوحيدة للتحقق من كلمة المرور.if password == self.__password:- نقارن كلمة المرور بشكل آمن داخل الصنف.def change_password(...):- تغيير كلمة المرور يتطلب معرفة القديمة أولاً.if len(new_password) >= 6:- نتحقق من قوة كلمة المرور الجديدة.
الأمان: كلمة المرور محمية بالكامل ولا يمكن الوصول إليها أو تعديلها إلا عبر دوال محددة!
4. أخطاء شائعة
الخطأ 1: الاعتقاد أن __ تمنع الوصول تماماً
في الحقيقة، يمكن الوصول للخصائص الخاصة باستخدام _ClassName__attribute، لكن هذا يعتبر سلوكاً سيئاً.
الحل: احترم اتفاقيات التسمية ولا تحاول الوصول للخصائص الخاصة من الخارج.
الخطأ 2: عدم التحقق من البيانات في Setters
إنشاء Setter دون التحقق من صحة البيانات يفقد التغليف فائدته.
الحل: دائماً تحقق من صحة البيانات قبل تعديل الخصائص الخاصة.
الخطأ 3: جعل كل شيء خاصاً
المبالغة في استخدام __ تجعل الكود معقداً بلا داعٍ.
الحل: استخدم __ فقط للبيانات الحساسة التي تحتاج حماية حقيقية.
5. نصائح مهمة
متى نستخدم التغليف؟
- عند التعامل مع بيانات حساسة (كلمات مرور، أرصدة مالية).
- عندما تريد التحكم في كيفية تعديل البيانات.
- لمنع تعديل البيانات بطرق قد تسبب أخطاء منطقية.
استخدم @property للكود الاحترافي
بدلاً من كتابة get_price() و set_price()، استخدم @property لجعل الكود أكثر طبيعية وسهولة في القراءة.
التوثيق مهم
اشرح في التعليقات لماذا جعلت خاصية معينة خاصة، وما هي القواعد التي يجب اتباعها عند تعديلها.
6. تمرين عملي
تحدي نظام الحجوزات
قم بإنشاء نظام حجز تذاكر يتبع الشروط التالية:
- أنشئ صنف
Ticketبخصائص خاصة: (السعر، عدد المقاعد المتاحة). - أضف دالة
book()لحجز تذكرة (تنقص المقاعد المتاحة). - أضف دالة
cancel()لإلغاء الحجز (تزيد المقاعد المتاحة). - استخدم
@propertyلقراءة عدد المقاعد المتاحة. - تأكد من عدم السماح بحجز أكثر من المقاعد المتاحة.
- تأكد من عدم السماح بإلغاء حجز غير موجود.
هذا التمرين سيثبت فهمك لمفهوم التغليف وحماية البيانات!
ملخص الدرس
- التغليف يحمي البيانات الحساسة من التعديل العشوائي.
- نستخدم
__قبل اسم الخاصية لجعلها خاصة. - Getters و Setters تسمح بالوصول الآمن للبيانات الخاصة.
@propertyطريقة احترافية لإنشاء Getters و Setters.- دائماً تحقق من صحة البيانات في Setters.
الخطوة التالية
بعد أن تعلمنا كيف نحمي البيانات، حان الوقت لنتعلم آخر ركيزة في OOP: تعدد الأشكال (Polymorphism)، الذي يسمح لنا باستخدام واجهة واحدة لأشكال مختلفة من البيانات.
الدرس التالي: تعدد الأشكال (Polymorphism) في Python