Kodax

تعدد الأشكال Polymorphism — OOP في C++

تعدد الأشكال (Polymorphism)

تعدد الأشكال يعني: كود واحد يتعامل مع أنواع مختلفة، وكل نوع يتصرف بطريقته. في C++ نوعان:

| النوع | وقت التحديد | الأدوات |

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

| ساكن (compile-time) | وقت الترجمة | تحميل الدوال، القوالب (templates) |

| ديناميكي (runtime) | وقت التشغيل | الدوال الافتراضية virtual |

هذا الدرس عن الديناميكي.

المشكلة بدون virtual

#include <iostream>
using namespace std;

class Shape {
public:
    double area() const { return 0; }          // ليست virtual
};
class Square : public Shape {
    double s;
public:
    explicit Square(double s) : s(s) {}
    double area() const { return s * s; }
};

int main() {
    Square sq(3);
    const Shape& ref = sq;
    cout << ref.area() << '\n';   // 0 !  — المترجم اختار حسب نوع المرجع (Shape)
}

الحل: virtual و override

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Shape {
public:
    virtual double area() const { return 0; }
    virtual ~Shape() = default;                // ضروري! انظر أدناه
};
class Square : public Shape {
    double s;
public:
    explicit Square(double s) : s(s) {}
    double area() const override { return s * s; }
};
class Circle : public Shape {
    double r;
public:
    explicit Circle(double r) : r(r) {}
    double area() const override { return 3.14159 * r * r; }
};

int main() {
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Square>(3));
    shapes.push_back(make_unique<Circle>(1));
    for (const auto& s : shapes)
        cout << s->area() << '\n';   // 9 ثم 3.14159 — كل شكل بطريقته
}

تعدد الأشكال يعمل فقط عبر مؤشر أو مرجع للأب.

لماذا override؟

override تطلب من المترجم التأكد أنك فعلاً تعيد تعريف دالة افتراضية. بدونها، خطأ صغير في التوقيع (نسيان const مثلاً) يُنشئ دالة جديدة بصمت، ويبقى الاستدعاء يذهب لنسخة الأب. اكتب override دائماً.

المدمّر الافتراضي — قاعدة إلزامية

إذا كان الصنف سيُحذف عبر مؤشر للأب (unique_ptr<Shape> أو delete basePtr)، يجب أن يكون مدمّر الأب virtual. بدونه يُستدعى مدمّر الأب فقط ← سلوك غير معرّف، وموارد الابن لا تُحرر.

القاعدة: أي صنف فيه دالة virtual ← مدمّره virtual.

التقطيع (Object Slicing)

#include <iostream>
using namespace std;

class Animal { public: virtual const char* sound() const { return "..."; } virtual ~Animal() = default; };
class Cat : public Animal { public: const char* sound() const override { return "مياو"; } };

void byValue(Animal a)      { cout << a.sound() << '\n'; }  // ❌ نسخة من جزء Animal فقط
void byRef(const Animal& a) { cout << a.sound() << '\n'; }  // ✅

int main() {
    Cat c;
    byValue(c);   // ...    (قُطع جزء Cat)
    byRef(c);     // مياو
}

مرّر الكائنات متعددة الأشكال بالمرجع أو المؤشر، وخزّنها في الحاويات كـ unique_ptr<Base> لا Base.

كيف يعمل من الداخل؟ (للفهم)

كل صنف فيه دوال virtual له جدول vtable بعناوين نسخه من الدوال، وكل كائن يحمل مؤشراً مخفياً إلى جدول صنفه. الاستدعاء الافتراضي = قفزة عبر الجدول. التكلفة صغيرة جداً، لكنها تمنع بعض التحسينات (inlining).

فخ: استدعاء virtual داخل المُنشئ

أثناء بناء جزء الأب، جزء الابن لم يُبنَ بعد، فالاستدعاء الافتراضي داخل مُنشئ الأب يذهب لنسخة الأب. لا تعتمد على virtual في المُنشئات والمدمّرات.

تمرين

شغّل المثال الكامل (أنظمة الدفع). أضف طريقة دفع جديدة Crypto دون تعديل PaymentProcessor — هذه هي قوة تعدد الأشكال: كود جديد يعمل مع كود قديم بلا تغيير.

#include <iostream>
#include <vector>
#include <memory>
using namespace std;

// ===== Compile-time Polymorphism =====

// Function Overloading
class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }
    
    double add(double a, double b) {
        return a + b;
    }
    
    int add(int a, int b, int c) {
        return a + b + c;
    }
    
    string add(const string& a, const string& b) {
        return a + b;
    }
};

// ===== Runtime Polymorphism =====

class PaymentMethod {
public:
    virtual void pay(double amount) = 0;
    virtual string getName() const = 0;
    virtual ~PaymentMethod() = default;
};

class CreditCard : public PaymentMethod {
private:
    string cardNumber;
public:
    CreditCard(const string& num) : cardNumber(num) {}
    
    void pay(double amount) override {
        cout << "💳 دفع $" << amount << " ببطاقة " 
             << cardNumber.substr(cardNumber.length()-4) << endl;
    }
    
    string getName() const override { return "بطاقة ائتمان"; }
};

class PayPal : public PaymentMethod {
private:
    string email;
public:
    PayPal(const string& e) : email(e) {}
    
    void pay(double amount) override {
        cout << "📧 دفع $" << amount << " عبر PayPal: " << email << endl;
    }
    
    string getName() const override { return "PayPal"; }
};

class Cash : public PaymentMethod {
public:
    void pay(double amount) override {
        cout << "💵 دفع $" << amount << " نقداً" << endl;
    }
    
    string getName() const override { return "نقدي"; }
};

// final - لمنع التعديل
class SecurePayment : public PaymentMethod {
public:
    void pay(double amount) final override {
        cout << "🔒 دفع آمن: $" << amount << endl;
    }
    
    string getName() const override { return "دفع آمن"; }
};

// معالج الدفع - يستخدم Polymorphism
class PaymentProcessor {
public:
    void processPayment(PaymentMethod* method, double amount) {
        cout << "معالجة الدفع عبر: " << method->getName() << endl;
        method->pay(amount);  // يستدعي النسخة الصحيحة!
        cout << "✅ تمت العملية بنجاح\n" << endl;
    }
};

int main() {
    // Compile-time Polymorphism
    cout << "=== Function Overloading ===" << endl;
    Calculator calc;
    cout << "add(5, 3) = " << calc.add(5, 3) << endl;
    cout << "add(2.5, 3.7) = " << calc.add(2.5, 3.7) << endl;
    cout << "add(1, 2, 3) = " << calc.add(1, 2, 3) << endl;
    cout << "add(مرحبا, عالم) = " << calc.add("مرحبا ", "عالم") << endl;
    
    // Runtime Polymorphism
    cout << "\n=== Runtime Polymorphism ===" << endl;
    
    vector<unique_ptr<PaymentMethod>> methods;
    methods.push_back(make_unique<CreditCard>("1234567890123456"));
    methods.push_back(make_unique<PayPal>("user@email.com"));
    methods.push_back(make_unique<Cash>());
    
    PaymentProcessor processor;
    
    for (auto& method : methods) {
        processor.processPayment(method.get(), 99.99);
    }
    
    return 0;
}
اختبر فهمك