Kodax

التغليف Encapsulation — OOP في C++

التغليف (Encapsulation)

التغليف ليس "اجعل كل شيء private واكتب get/set لكل حقل". الهدف الحقيقي: الصنف مسؤول عن صحة بياناته. هناك قواعد (تسمى invariants) يجب أن تبقى صحيحة دائماً، والطريقة الوحيدة لتغيير البيانات هي عبر دوال تحترم هذه القواعد.

مثال: لماذا public خطر؟

struct Account {
    double balance;     // أي كود يستطيع كتابة balance = -1000000
};

القاعدة "الرصيد لا يصبح سالباً" لا يحميها أحد.

الحل

#include <iostream>
#include <string>
using namespace std;

class Account {
    string owner;
    double balance = 0;          // private افتراضياً في class
public:
    explicit Account(string name) : owner(name) {}

    bool deposit(double amount) {
        if (amount <= 0) return false;
        balance += amount;
        return true;
    }
    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;   // القاعدة محمية هنا
        balance -= amount;
        return true;
    }
    double getBalance() const { return balance; }
};

int main() {
    Account acc("ليلى");
    acc.deposit(100);
    cout << boolalpha << acc.withdraw(500) << '\n';   // false — رُفضت العملية
    cout << acc.getBalance() << '\n';                  // 100
}

لا يوجد setBalance! تغيير الرصيد له معنى تجاري (إيداع/سحب)، فنكشف هذه العمليات فقط.

مستويات الوصول

| | داخل الصنف | الأصناف المشتقة | خارج الصنف |

|---|---|---|---|

| public | ✅ | ✅ | ✅ |

| protected | ✅ | ✅ | ❌ |

| private | ✅ | ❌ | ❌ |

ملاحظة: private تحمي على مستوى الصنف لا الكائن؛ دالة في Account تستطيع قراءة other.balance لكائن Account آخر.

متى نكتب getter و setter؟

- getter: عندما يحتاج المستخدم القيمة فعلاً. أرجع بالقيمة للأنواع الصغيرة، و const string& للكبيرة.

- setter: فقط إذا كان التغيير الحر منطقياً، ومع التحقق دائماً. إذا وجدت نفسك تكتب set لكل حقل دون تحقق، فالحقل عملياً public.

التحقق في المُنشئ

القاعدة يجب أن تصح منذ لحظة الإنشاء:

class Temperature {
    double celsius;
public:
    explicit Temperature(double c) : celsius(c < -273.15 ? -273.15 : c) {}  // لا أقل من الصفر المطلق
    double get() const { return celsius; }
};

في دورة C++ المتقدم ستتعلم رمي استثناء (exception) عند مدخلات غير صالحة بدلاً من التصحيح الصامت.

صحة const (const-correctness)

- دوال القراءة const.

- مرّر الكائنات للدوال بـ const T& عندما لا تعدّلها.

- لا تُرجع مرجعاً غير const إلى عضو private — فهذا يفتح الباب للتعديل من الخارج:

#include <vector>
using namespace std;

class Team {
    vector<int> scores;
public:
    vector<int>& raw() { return scores; }               // ❌ يكسر التغليف: أي أحد يعدّل
    const vector<int>& view() const { return scores; }  // ✅ قراءة فقط
    void addScore(int s) { if (s >= 0) scores.push_back(s); }
};

فوائد التغليف

1. صحة البيانات: كل التغييرات تمر بنقطة تحقق واحدة.

2. حرية التغيير: يمكنك تغيير التمثيل الداخلي (مثلاً تخزين الرصيد بالقروش كـ long long) دون كسر كود من يستخدم الصنف.

3. سهولة التتبع: إذا فسدت القيمة، تعرف أين تبحث.

تمرين

اكتب صنف Clock يخزن الساعة (0–23) والدقيقة (0–59). وفر tick() تضيف دقيقة (مع الانتقال الصحيح من 23:59 إلى 00:00)، و print() const. تأكد أن المُنشئ يرفض/يصحح القيم الخارجة عن المدى.

#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:
    string accountNumber;
    string ownerName;
    double balance;
    string pin;
    
    // دالة خاصة للتحقق
    bool isValidAmount(double amount) const {
        return amount > 0;
    }

public:
    // Constructor
    BankAccount(string accNum, string name, string pinCode, double initial = 0)
        : accountNumber(accNum), ownerName(name), pin(pinCode), balance(initial) {}
    
    // Getters
    string getAccountNumber() const { 
        // إخفاء جزء من الرقم
        return "****" + accountNumber.substr(accountNumber.length() - 4);
    }
    
    string getOwnerName() const { return ownerName; }
    
    double getBalance() const { return balance; }
    
    // Setters مع تحقق
    bool setOwnerName(const string& name) {
        if (name.empty()) {
            cout << "❌ الاسم لا يمكن أن يكون فارغاً" << endl;
            return false;
        }
        ownerName = name;
        return true;
    }
    
    // العمليات المحمية
    bool deposit(double amount) {
        if (!isValidAmount(amount)) {
            cout << "❌ المبلغ غير صالح" << endl;
            return false;
        }
        balance += amount;
        cout << "✅ تم إيداع $" << amount << endl;
        return true;
    }
    
    bool withdraw(double amount, const string& inputPin) {
        // التحقق من PIN
        if (inputPin != pin) {
            cout << "❌ رمز PIN خاطئ" << endl;
            return false;
        }
        
        if (!isValidAmount(amount)) {
            cout << "❌ المبلغ غير صالح" << endl;
            return false;
        }
        
        if (amount > balance) {
            cout << "❌ رصيد غير كافٍ" << endl;
            return false;
        }
        
        balance -= amount;
        cout << "✅ تم سحب $" << amount << endl;
        return true;
    }
    
    bool changePin(const string& oldPin, const string& newPin) {
        if (oldPin != pin) {
            cout << "❌ رمز PIN القديم خاطئ" << endl;
            return false;
        }
        if (newPin.length() != 4) {
            cout << "❌ PIN يجب أن يكون 4 أرقام" << endl;
            return false;
        }
        pin = newPin;
        cout << "✅ تم تغيير PIN بنجاح" << endl;
        return true;
    }
    
    void displayInfo() const {
        cout << "\n=== معلومات الحساب ===" << endl;
        cout << "رقم الحساب: " << getAccountNumber() << endl;
        cout << "صاحب الحساب: " << ownerName << endl;
        cout << "الرصيد: $" << balance << endl;
    }
};

int main() {
    BankAccount account("1234567890", "أحمد محمد", "1234", 1000);
    
    account.displayInfo();
    
    // محاولات آمنة
    account.deposit(500);
    account.withdraw(200, "1234");  // PIN صحيح
    account.withdraw(100, "0000");  // PIN خاطئ
    account.withdraw(5000, "1234"); // رصيد غير كافٍ
    
    // تغيير PIN
    account.changePin("1234", "5678");
    
    account.displayInfo();
    
    // لا يمكن الوصول للـ private مباشرة:
    // account.balance = 1000000;  // خطأ!
    // account.pin = "0000";       // خطأ!
    
    return 0;
}
اختبر فهمك