Kodax

التجريد Abstraction — OOP في C++

التجريد (Abstraction)

التجريد يعني إظهار ماذا يفعل الشيء وإخفاء كيف. في الدرس السابق كانت Shape::area() تعيد 0 — قيمة لا معنى لها. الأصح أن نقول: "كل شكل يجب أن يحدد مساحته، ولا يوجد شكل عام".

الدالة الافتراضية الخالصة

class Shape {
public:
    virtual double area() const = 0;     // = 0 ← خالصة: بلا تنفيذ هنا
    virtual ~Shape() = default;
};

- الصنف الذي فيه دالة خالصة واحدة على الأقل يصبح مجرداً (abstract): لا يمكن إنشاء كائن منه.

- كل صنف مشتق يجب أن ينفذ الدوال الخالصة، وإلا يبقى مجرداً هو أيضاً.

class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};
int main() {
    Shape s;   // خطأ: cannot declare variable 's' to be of abstract type
}

الواجهة (Interface)

C++ لا تملك كلمة interface. الواجهة = صنف مجرد فيه دوال خالصة فقط ومدمّر افتراضي، بلا بيانات. تصف قدرة:

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

class Notifier {                                   // واجهة
public:
    virtual void send(const string& to, const string& msg) = 0;
    virtual ~Notifier() = default;
};

class EmailNotifier : public Notifier {
public:
    void send(const string& to, const string& msg) override { cout << "📧 " << to << ": " << msg << '\n'; }
};
class SmsNotifier : public Notifier {
public:
    void send(const string& to, const string& msg) override { cout << "📱 " << to << ": " << msg << '\n'; }
};

// هذه الدالة لا تعرف ولا تهتم بنوع الإشعار
void remindAll(Notifier& n, const vector<string>& users) {
    for (const auto& u : users) n.send(u, "موعد الاختبار غداً");
}

int main() {
    EmailNotifier email;
    SmsNotifier sms;
    remindAll(email, {"sara@x.com", "omar@x.com"});
    remindAll(sms, {"0770000000"});
}

البرمجة نحو الواجهة

remindAll تعتمد على الواجهة Notifier لا على صنف محدد. النتائج:

- إضافة WhatsAppNotifier لا تحتاج تعديل remindAll.

- في الاختبارات يمكن تمرير FakeNotifier يسجّل الرسائل بدل إرسالها فعلاً.

هذا جوهر مبدأ عكس الاعتماد (Dependency Inversion): الكود عالي المستوى يعتمد على تجريدات، لا تفاصيل.

صنف مجرد مع تنفيذ جزئي

الصنف المجرد يمكن أن يحتوي بيانات ودوالاً عادية أيضاً — مفيد لمشاركة الكود المشترك:

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

class Report {
public:
    void print() const {                      // خطوات ثابتة
        cout << "=== " << title() << " ===\n";
        body();
        cout << "-- نهاية --\n";
    }
    virtual ~Report() = default;
protected:
    virtual string title() const = 0;         // خطوات يحددها كل ابن
    virtual void body() const = 0;
};

هذا نمط Template Method: الأب يحدد الهيكل والأبناء يملؤون الفراغات.

مقارنة سريعة

| | صنف عادي | صنف مجرد | واجهة |

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

| إنشاء كائن مباشرة | ✅ | ❌ | ❌ |

| دوال خالصة | لا | واحدة على الأقل | كلها |

| بيانات | ✅ | ✅ | لا (عرفاً) |

تمرين

في المثال الكامل، أضف شكلاً جديداً Square ينفذ IDrawable و IResizable. ماذا يحدث إذا نسيت تنفيذ resize؟ اقرأ رسالة المترجم.

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

// ===== Interface =====
class IDrawable {
public:
    virtual void draw() const = 0;
    virtual ~IDrawable() = default;
};

class IResizable {
public:
    virtual void resize(double factor) = 0;
    virtual ~IResizable() = default;
};

// ===== Abstract Class =====
class Shape : public IDrawable, public IResizable {
protected:
    string color;
    double x, y;  // الموقع

public:
    Shape(const string& c, double px, double py) 
        : color(c), x(px), y(py) {}
    
    // دوال محققة
    void setColor(const string& c) { color = c; }
    string getColor() const { return color; }
    
    void move(double dx, double dy) {
        x += dx;
        y += dy;
    }
    
    // دوال مجردة (يجب تحقيقها)
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
    
    // معلومات الشكل
    virtual void info() const {
        cout << "اللون: " << color << ", الموقع: (" << x << ", " << y << ")" << endl;
    }
    
    virtual ~Shape() = default;
};

// ===== Concrete Classes =====
class Circle : public Shape {
private:
    double radius;

public:
    Circle(const string& c, double px, double py, double r)
        : Shape(c, px, py), radius(r) {}
    
    double area() const override {
        return 3.14159 * radius * radius;
    }
    
    double perimeter() const override {
        return 2 * 3.14159 * radius;
    }
    
    void draw() const override {
        cout << "🔵 رسم دائرة " << color << " بنصف قطر " << radius << endl;
    }
    
    void resize(double factor) override {
        radius *= factor;
        cout << "تم تغيير حجم الدائرة، النصف الجديد: " << radius << endl;
    }
    
    void info() const override {
        Shape::info();
        cout << "نصف القطر: " << radius << ", المساحة: " << area() << endl;
    }
};

class Rectangle : public Shape {
private:
    double width, height;

public:
    Rectangle(const string& c, double px, double py, double w, double h)
        : Shape(c, px, py), width(w), height(h) {}
    
    double area() const override {
        return width * height;
    }
    
    double perimeter() const override {
        return 2 * (width + height);
    }
    
    void draw() const override {
        cout << "🟦 رسم مستطيل " << color << " (" << width << "x" << height << ")" << endl;
    }
    
    void resize(double factor) override {
        width *= factor;
        height *= factor;
        cout << "تم تغيير الحجم: " << width << "x" << height << endl;
    }
};

class Triangle : public Shape {
private:
    double base, height;

public:
    Triangle(const string& c, double px, double py, double b, double h)
        : Shape(c, px, py), base(b), height(h) {}
    
    double area() const override {
        return 0.5 * base * height;
    }
    
    double perimeter() const override {
        // تقريب لمثلث متساوي الساقين
        double side = sqrt(height*height + (base/2)*(base/2));
        return base + 2 * side;
    }
    
    void draw() const override {
        cout << "🔺 رسم مثلث " << color << endl;
    }
    
    void resize(double factor) override {
        base *= factor;
        height *= factor;
    }
};

int main() {
    // لا يمكن إنشاء Shape مباشرة:
    // Shape s("red", 0, 0);  // خطأ!
    
    // مصفوفة من أشكال مختلفة
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Circle>("أحمر", 0, 0, 5));
    shapes.push_back(make_unique<Rectangle>("أزرق", 10, 10, 4, 6));
    shapes.push_back(make_unique<Triangle>("أخضر", 5, 5, 3, 4));
    
    cout << "=== جميع الأشكال ===" << endl;
    for (const auto& shape : shapes) {
        shape->draw();
        shape->info();
        cout << endl;
    }
    
    // تغيير الحجم
    cout << "=== بعد تغيير الحجم ===" << endl;
    for (auto& shape : shapes) {
        shape->resize(2);
    }
    
    // حساب المساحة الكلية
    double totalArea = 0;
    for (const auto& shape : shapes) {
        totalArea += shape->area();
    }
    cout << "\nالمساحة الكلية: " << totalArea << endl;
    
    return 0;
}
اختبر فهمك