التجريد 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;
}اختبر فهمك