الواجهات في C++: دليل لمن سئم من البحث
\n\nإذا سبق لك البرمجة بلغة Java أو C# ثم انتقلت إلى C++، فبالتأكيد تساءلت: "أين الكلمة المفتاحية interface؟". المفاجأة: لا توجد. في C++، يتم تنفيذ الواجهات بطريقة مختلفة — من خلال الفئات المجردة. وهذه ليست مشكلة، بل ميزة تمنح مرونة لا تصدق.
\n\nعلى موقع Stack Overflow، سؤال "How do you declare an interface in C++?" يجمع آلاف المشاهدات. هذا يعني أنه حتى المطورين ذوي الخبرة يختلط عليهم الأمر أحيانًا. اليوم سنفهم مرة واحدة وإلى الأبد كيفية التصريح عن واجهة في C++ بحيث يكون كودك نظيفًا والمترجم راضيًا.
\n\nما هي الواجهة في C++؟ (المفاجأة: إنها فئة مجردة)
\n\nفي البرمجة الكائنية التقليدية، الواجهة هي عقد. الفئة التي تنفذ الواجهة ملزمة بتوفير تنفيذ لجميع دوالها. في C++، يقوم بهذا الدور الفئة المجردة — وهي فئة تحتوي على دالة واحدة على الأقل افتراضية بحتة (pure virtual function).
\n\nتذكر القاعدة الأساسية:
\n- \n
- الواجهة = فئة حيث جميع الدوال افتراضية بحتة. \n
- الفئة المجردة = فئة حيث دالة واحدة على الأقل افتراضية بحتة (ولكن قد تحتوي على دوال عادية مع تنفيذ). \n
بالنسبة للواجهة النقية، نستخدم الخيار الأول: لا منطق، فقط تصريحات.
\n\nكيفية التصريح عن واجهة نقية: الصيغة الأساسية
\n\nإليك مثال بسيط لواجهة شكل هندسي:
\n\nclass IShape {\npublic:\n virtual double area() const = 0; // دالة افتراضية بحتة\n virtual double perimeter() const = 0; // دالة أخرى\n virtual ~IShape() {} // مدمر افتراضي — إلزامي!\n};\n\nلاحظ = 0 في نهاية التصريح. هذه هي العلامة السحرية "افتراضي بحت". هذه الدالة ليس لها جسم (تنفيذ) في الفئة الأساسية.
النقاط الرئيسية:
\n- \n
- البادئة I في اسم الفئة — اتفاقية غير رسمية للواجهات (I = Interface). يمكنك عدم استخدامها، لكنها تسهل قراءة الكود. \n
- المدمر الافتراضي إلزامي. إذا قمت بحذف كائن عبر مؤشر للواجهة، بدون مدمر افتراضي سيكون السلوك غير محدد (اقرأ: البرنامج سيتعطل). \n
- جميع الدوال public — يجب أن تكون الواجهة قابلة للاستدعاء من الخارج. \n
تنفيذ الواجهة: من الأقوال إلى الأفعال
\n\nالآن لنكتب فئة تنفذ واجهتنا IShape. لتكن دائرة:
class Circle : public IShape {\nprivate:\n double radius;\npublic:\n explicit Circle(double r) : radius(r) {}\n\n double area() const override {\n return 3.14159 * radius * radius;\n }\n\n double perimeter() const override {\n return 2 * 3.14159 * radius;\n }\n};\n\nالكلمة المفتاحية override (C++11 والإصدارات الأحدث) ليست إلزامية، لكنها مرغوبة بشدة. فهي تجبر المترجم على التحقق من أنك تعيد تعريف دالة افتراضية من الفئة الأساسية. أخطأت في الاسم؟ سيقول المترجم: "خطأ! لا توجد هذه الدالة في الواجهة".
تعدد الأشكال في العمل
\n\nالآن الجزء الممتع — يمكننا العمل مع أشكال مختلفة من خلال واجهة موحدة:
\n\nvoid printShapeInfo(const IShape& shape) {\n std::cout << "Area: " << shape.area() << "\\n";\n std::cout << "Perimeter: " << shape.perimeter() << "\\n";\n}\n\nint main() {\n Circle c(5.0);\n printShapeInfo(c); // يعمل!\n return 0;\n}\n\nالدالة printShapeInfo تستقبل مرجعًا إلى IShape. لا يهمها أي كائن تم تمريره — دائرة، مربع أو مثلث. طالما أنها تنفذ الواجهة، كل شيء سيعمل. هذا هو تعدد الأشكال