التغليف (Encapsulation) — البرمجة الكائنية
التغليف (Encapsulation) والخصائص
التغليف يعني أن الكائن يتحكم في بياناته: بقية البرنامج تتعامل معه عبر دوال واضحة، بدلاً من تعديل خصائصه الداخلية مباشرة إلى قيم غير صالحة.
المشكلة
class Account:
def __init__(self):
self.balance = 0
account = Account()
account.balance = -5000 # لا شيء يمنع هذا!مستويات الوصول في Python
Python لا تملك private حقيقية مثل Java، بل اصطلاحات:
| الكتابة | المعنى |
|---|---|
| balance | عام: جزء من واجهة الصنف |
| _balance | داخلي بالاتفاق: "لا تستخدمه من الخارج" لكنه متاح |
| __balance | تشويه الاسم (name mangling): يصبح _Account__balance |
__balance ليست مخفية فعلاً: Python تغيّر اسمها داخلياً إلى _Account__balance لتجنب التصادم مع أسماء في الأصناف الوارثة، ويمكن الوصول إليها بهذا الاسم. الاصطلاح الشائع في Python هو _ واحدة.
@property: خاصية بقواعد
property تجعل دالة تُستخدم كأنها خاصية، فتستطيع التحقق عند القراءة والكتابة دون تغيير طريقة الاستخدام:
class Account:
def __init__(self, balance=0):
self._balance = balance
@property
def balance(self): # القراءة: account.balance
return self._balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("المبلغ يجب أن يكون موجباً")
self._balance += amountبدون setter، تصبح balance للقراءة فقط: account.balance = 10 ترمي AttributeError. الرصيد يتغير فقط عبر deposit و withdraw اللتين تطبقان القواعد.
setter عند الحاجة
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("عمر غير صالح")
self._age = valueاستدعِ الـ setter داخل __init__ أيضاً (self.age = age) حتى يُطبَّق التحقق من البداية.
خصائص محسوبة
@property
def full_name(self):
return f"{self.first} {self.last}"لا تُخزَّن، بل تُحسب عند كل قراءة، فتبقى صحيحة دائماً.
لماذا لا نكتب get_balance() و set_balance()؟
في Python ابدأ بخاصية عامة بسيطة؛ إذا احتجت لاحقاً قواعد، حوّلها إلى @property دون أن يتغير أي كود يستخدمها. هذا أحد أجمل جوانب Python.
تمرين
أنشئ صنف Temperature يخزن الحرارة بالمئوية في _celsius مع setter يرفض القيم الأقل من -273.15، وخاصية محسوبة fahrenheit.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # Public
self._account_type = "عادي" # Protected (اصطلاح)
self.__balance = balance # Private
# Getter
@property
def balance(self):
return self.__balance
# Setter مع تحقق
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("الرصيد لا يمكن أن يكون سالباً!")
self.__balance = value
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return f"تم إيداع {amount}. الرصيد: {self.__balance}"
return "مبلغ غير صالح"
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return f"تم سحب {amount}. الرصيد: {self.__balance}"
return "رصيد غير كافٍ أو مبلغ غير صالح"
# استخدام
account = BankAccount("أحمد", 1000)
# الوصول للـ public
print(account.owner) # أحمد
# الوصول عبر property
print(account.balance) # 1000
# محاولة تعديل private مباشرة - لا تعمل!
# print(account.__balance) # AttributeError!
# الطريقة الصحيحة
account.deposit(500)
print(account.balance) # 1500
# Setter مع تحقق
try:
account.balance = -100 # ValueError!
except ValueError as e:
print(e)
# مثال آخر: شخص
class Person:
def __init__(self, name, age):
self._name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if 0 < value < 150:
self.__age = value
else:
raise ValueError("عمر غير صالح")
# Property للقراءة فقط
@property
def is_adult(self):
return self.__age >= 18
person = Person("محمد", 25)
print(person.is_adult) # True
# person.is_adult = False # Error! قراءة فقطاختبر فهمك