تصميم الواجهات (Interface) في C++
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 61 من 104.
الواجهة interface في C++ هي فئة abstract تكون فيها كل دالة عضو—باستثناء المدمّر (virtual)—افتراضية خالصة. وعلى عكس الفئات abstract التي قد تحتوي على بعض التنفيذ، تحدد الواجهات العمليات التي يجب أن تكون موجودة فقط؛ فهي تحدد عقدًا دون أي سلوك.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};تضمن واجهة Drawable هذه أن أي class تطبقها ستحتوي على الطريقتين draw() وresize()، لكنها لا تقول شيئًا عن كيفية عملهما. هذا الفصل قوي - إذ يمكن للكود الاعتماد على الواجهة دون معرفة أي شيء عن الأنواع الملموسة.
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};
class Button : public Drawable {
std::string label;
public:
Button(std::string l) : label(l) {}
void draw() override { std::cout << "Drawing button: " << label << std::endl; }
void resize(double factor) override { /* تغيير حجم الزر */ }
};يمكن لفئة تنفيذ واجهات متعددة، مما يتيح تصميمات مرنة يمكن للكائنات فيها أداء أدوار مختلفة:
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};تُعزّز الواجهات الاقترانَ الضعيف — إذ يعتمد كودك على التجريدات بدلًا من التطبيقات الملموسة، مما يجعل توسيعه واختباره أسهل.
التحدي
سهللنَبْنِ نظامًا لإدارة الأجهزة يوضّح كيفية تعريف الواجهات لعقود لأنواع مختلفة من الأجهزة. ستنشئ واجهتين منفصلتين يمكن للأجهزة تنفيذهما، ثم تبني فئات أجهزة ملموسة تفي بأحد هذين العقدين أو كليهما.
ستنظّم التعليمات البرمجية عبر ثلاثة ملفات:
Interfaces.h: عرّف فئتين مجردتين تمامًا (واجهتين) تمثلان إمكانات مختلفة للأجهزة:Powerable: أي جهاز يمكن تشغيله وإيقافه:- طريقة افتراضية خالصة
powerOn() - طريقة افتراضية خالصة
powerOff() - طريقة افتراضية خالصة
getPowerStatus()تُرجع قيمة من النوعstd::string - مدمّر افتراضي
Connectable: أي جهاز يمكنه الاتصال بشبكة:- طريقة افتراضية خالصة
connect(const std::string& network) - طريقة افتراضية خالصة
disconnect() - طريقة افتراضية خالصة
getConnectionInfo()تُرجع قيمة من النوعstd::string - مدمّر افتراضي
- طريقة افتراضية خالصة
Devices.h: نفّذ فئات أجهزة ملموسة تستخدم هذه الواجهات:Lamp: تنفّذPowerableفقط:- عضو خاص
bool isOn(يبدأ بالقيمةfalse) - عضو خاص
std::string name - منشئ يستقبل اسم المصباح
powerOn()تضبطisOnعلى true وتطبع:<name>: Light turned onpowerOff()تضبطisOnعلى false وتطبع:<name>: Light turned offgetPowerStatus()تُرجع"ON"أو"OFF"استنادًا إلى الحالة
SmartTV: تنفّذ كلًا منPowerableوConnectable:- أعضاء خاصة:
bool isOn(يبدأ بالقيمة false)، وstd::string brand، وstd::string currentNetwork(يبدأ فارغًا) - منشئ يستقبل العلامة التجارية للتلفاز
powerOn()تضبطisOnعلى true وتطبع:<brand> TV: Powered onpowerOff()تضبطisOnعلى false، وتمسح الشبكة، وتطبع:<brand> TV: Powered offgetPowerStatus()تُرجع"ON"أو"OFF"connect()تخزّن اسم الشبكة وتطبع:<brand> TV: Connected to <network>disconnect()تمسح الشبكة وتطبع:<brand> TV: DisconnectedgetConnectionInfo()تُرجع"Connected to <network>"إذا كان متصلًا، أو"Not connected"إذا كانت فارغة
- عضو خاص
main.cpp: اقرأ ثلاث مدخلات (كل منها في سطر منفصل):- اسم المصباح
- العلامة التجارية للتلفاز
- اسم الشبكة
أنشئ كائنًا من
Lampوكائنًا منSmartTV. وضّح كيفية استخدام الواجهة نفسها مع أجهزة مختلفة:أولًا، تعامل مع الجهازين من خلال واجهة
Powerable. خزّن مؤشرين إلى كليهما في مصفوفة من النوعPowerable*، ثم نفّذ حلقة واستدعِpowerOn()لكل منهما، ثم اطبع حالتهما بالتنسيق:Status: <powerStatus>اطبع سطرًا فارغًا، ثم تعامل مع SmartTV من خلال واجهة
Connectable. أنشئ مؤشرًا من النوعConnectable*إلى SmartTV، واستدعِconnect()باستخدام اسم الشبكة، واطبع:Connection: <connectionInfo>اطبع سطرًا فارغًا آخر، ثم أوقف تشغيل الجهازين من خلال مصفوفة
Powerableواعرض حالتهما النهائية.
على سبيل المثال، عند استخدام المدخلات Desk Lamp وSamsung وHomeWiFi:
Desk Lamp: Light turned on
Status: ON
Samsung TV: Powered on
Status: ON
Samsung TV: Connected to HomeWiFi
Connection: Connected to HomeWiFi
Desk Lamp: Light turned off
Status: OFF
Samsung TV: Powered off
Status: OFFلاحظ كيف يمكن التعامل مع SmartTV باعتباره إما Powerable أو Connectable، اعتمادًا على مؤشر الواجهة الذي تستخدمه. هذه المرونة هي قوة تنفيذ واجهات متعددة. يمكن للتعليمات البرمجية الخاصة بك العمل مع أي جهاز يفي بالعقد الذي تحتاج إليه، من دون معرفة النوع الملموس.
جرّب بنفسك
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// اقرأ المدخلات
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: أنشئ كائن Lamp وكائن SmartTV
// TODO: أنشئ مصفوفة من مؤشرات Powerable* تحتوي على كلا الجهازين
// TODO: مر عبر المصفوفة واستدعِ powerOn() على كل جهاز
// بعد كل powerOn()، اطبع: Status: <powerStatus>
// TODO: اطبع سطراً فارغاً
// TODO: أنشئ مؤشراً Connectable* إلى SmartTV
// استدعِ connect() مع اسم الشبكة
// Print: Connection: <connectionInfo>
// TODO: اطبع سطراً فارغاً
// TODO: مر عبر مصفوفة Powerable واستدعِ powerOff() على كل جهاز
// بعد كل powerOff()، اطبع: Status: <powerStatus>
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت