التغليف (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. أمثلة تطبيقية

المثال الأول: التغليف الأساسي

لنبدأ بمثال بسيط يوضح الفرق بين الخصائص العامة والخاصة.

basic_encapsulation.py
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)
النتيجة
صاحب الحساب: محمد الرصيد: 1000 تم إيداع 500 ريال
شرح الكود سطراً بسطر:
  1. class BankAccount: - نعرّف صنف الحساب البنكي.
  2. self.owner = owner - خاصية عامة يمكن الوصول إليها مباشرة.
  3. self.__balance = balance - خاصية خاصة (لاحظ الشرطتين __) لحماية الرصيد.
  4. def get_balance(self): - دالة للوصول الآمن للرصيد (Getter).
  5. def deposit(self, amount): - دالة للإيداع مع التحقق من صحة المبلغ.
  6. if amount > 0: - نتحقق أن المبلغ موجب قبل الإيداع.
  7. print(account.owner) - نصل للخاصية العامة مباشرة (مسموح).
  8. account.get_balance() - نستخدم الدالة للوصول للرصيد (الطريقة الصحيحة).

الفائدة: الرصيد محمي ولا يمكن تعديله إلا عبر دوال محددة تتحقق من صحة العملية!

المثال الثاني: Getters و Setters

في شرح البرمجة باللغة العربية، نتعلم استخدام دوال خاصة للقراءة (Getter) والكتابة (Setter) مع التحقق من البيانات.

getters_setters.py
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("")  # سيرفض التعديل
النتيجة
الاسم: سارة العمر: 20 العمر الجديد: 21 العمر غير منطقي! الاسم لا يمكن أن يكون فارغاً!
شرح الكود سطراً بسطر:
  1. self.__name = name - نخزن الاسم كخاصية خاصة.
  2. self.__age = age - نخزن العمر كخاصية خاصة.
  3. def get_name(self): - Getter لقراءة الاسم.
  4. def set_name(self, name): - Setter لتعديل الاسم مع التحقق.
  5. if len(name) > 0: - نتحقق أن الاسم ليس فارغاً.
  6. if 5 <= age <= 100: - نتحقق أن العمر في نطاق منطقي.
  7. student.set_age(150) - محاولة تعديل خاطئ سيتم رفضها.

الفكرة: نتحكم في كيفية تعديل البيانات ونمنع القيم الخاطئة!

المثال الثالث: استخدام @property (الطريقة الاحترافية)

بايثون توفر طريقة أنيقة لإنشاء Getters و Setters باستخدام @property.

property_decorator.py
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
النتيجة
المنتج: لابتوب السعر: 3000 ريال السعر الجديد: 2500 ريال السعر لا يمكن أن يكون سالباً!
شرح الكود سطراً بسطر:
  1. self.__price = price - نخزن السعر كخاصية خاصة.
  2. @property - Decorator يحول الدالة إلى Getter.
  3. def price(self): - دالة تُستدعى عند قراءة price.
  4. @price.setter - Decorator يحول الدالة إلى Setter.
  5. def price(self, value): - دالة تُستدعى عند تعديل price.
  6. product.price = 2500 - نعدل السعر كأنه خاصية عادية، لكن Setter يعمل في الخلفية!

الميزة: الكود يبدو بسيطاً وطبيعياً، لكن الحماية موجودة في الخلفية!

المثال الرابع: تطبيق واقعي - نظام المستخدمين

مثال عملي يجمع كل ما تعلمناه في نظام إدارة مستخدمين.

user_system.py
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()
النتيجة
كلمة المرور خاطئة! مرحباً ahmed! تم تسجيل الدخول بنجاح كلمة المرور الجديدة قصيرة جداً! تم تغيير كلمة المرور بنجاح! تم تسجيل الخروج
شرح الكود سطراً بسطر:
  1. self.__password = password - كلمة المرور مخفية تماماً ولا يمكن الوصول إليها.
  2. self.__is_logged_in = False - حالة تسجيل الدخول محمية.
  3. def login(self, password): - الطريقة الوحيدة للتحقق من كلمة المرور.
  4. if password == self.__password: - نقارن كلمة المرور بشكل آمن داخل الصنف.
  5. def change_password(...): - تغيير كلمة المرور يتطلب معرفة القديمة أولاً.
  6. if len(new_password) >= 6: - نتحقق من قوة كلمة المرور الجديدة.

الأمان: كلمة المرور محمية بالكامل ولا يمكن الوصول إليها أو تعديلها إلا عبر دوال محددة!

4. أخطاء شائعة

الخطأ 1: الاعتقاد أن __ تمنع الوصول تماماً

في الحقيقة، يمكن الوصول للخصائص الخاصة باستخدام _ClassName__attribute، لكن هذا يعتبر سلوكاً سيئاً.

الحل: احترم اتفاقيات التسمية ولا تحاول الوصول للخصائص الخاصة من الخارج.

الخطأ 2: عدم التحقق من البيانات في Setters

إنشاء Setter دون التحقق من صحة البيانات يفقد التغليف فائدته.

الحل: دائماً تحقق من صحة البيانات قبل تعديل الخصائص الخاصة.

الخطأ 3: جعل كل شيء خاصاً

المبالغة في استخدام __ تجعل الكود معقداً بلا داعٍ.

الحل: استخدم __ فقط للبيانات الحساسة التي تحتاج حماية حقيقية.

5. نصائح مهمة

متى نستخدم التغليف؟
  • عند التعامل مع بيانات حساسة (كلمات مرور، أرصدة مالية).
  • عندما تريد التحكم في كيفية تعديل البيانات.
  • لمنع تعديل البيانات بطرق قد تسبب أخطاء منطقية.
استخدم @property للكود الاحترافي

بدلاً من كتابة get_price() و set_price()، استخدم @property لجعل الكود أكثر طبيعية وسهولة في القراءة.

التوثيق مهم

اشرح في التعليقات لماذا جعلت خاصية معينة خاصة، وما هي القواعد التي يجب اتباعها عند تعديلها.

6. تمرين عملي

تحدي نظام الحجوزات

قم بإنشاء نظام حجز تذاكر يتبع الشروط التالية:

  1. أنشئ صنف Ticket بخصائص خاصة: (السعر، عدد المقاعد المتاحة).
  2. أضف دالة book() لحجز تذكرة (تنقص المقاعد المتاحة).
  3. أضف دالة cancel() لإلغاء الحجز (تزيد المقاعد المتاحة).
  4. استخدم @property لقراءة عدد المقاعد المتاحة.
  5. تأكد من عدم السماح بحجز أكثر من المقاعد المتاحة.
  6. تأكد من عدم السماح بإلغاء حجز غير موجود.

هذا التمرين سيثبت فهمك لمفهوم التغليف وحماية البيانات!

ملخص الدرس
  • التغليف يحمي البيانات الحساسة من التعديل العشوائي.
  • نستخدم __ قبل اسم الخاصية لجعلها خاصة.
  • Getters و Setters تسمح بالوصول الآمن للبيانات الخاصة.
  • @property طريقة احترافية لإنشاء Getters و Setters.
  • دائماً تحقق من صحة البيانات في Setters.

الخطوة التالية

بعد أن تعلمنا كيف نحمي البيانات، حان الوقت لنتعلم آخر ركيزة في OOP: تعدد الأشكال (Polymorphism)، الذي يسمح لنا باستخدام واجهة واحدة لأشكال مختلفة من البيانات.

الدرس التالي: تعدد الأشكال (Polymorphism) في Python
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن