تصميم الواجهات (Interface) في C++
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 61 من 104.
تُعد الـ interface في C++ فئة مجردة (abstract class) حيث تكون جميع الدوال الأعضاء (member functions) هي pure virtual. وخلافاً للفئات المجردة (abstract classes) التي قد تحتوي على بعض التنفيذ (implementation)، فإن الواجهات (interfaces) تُحدد فقط العمليات التي يجب أن تكون موجودة - فهي تُحدد عقداً (contract) دون أي سلوك.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};تضمن واجهة Drawable هذه أن أي فئة (class) تقوم بتنفيذها ستحتوي على دالتي draw() و resize()، ولكنها لا تذكر شيئاً عن كيفية عملهما. هذا الفصل قوي - حيث يمكن للكود أن يعتمد على الواجهة دون معرفة أي شيء عن الأنواع الملموسة (concrete types).
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) تنفيذ واجهات (interfaces) متعددة، مما يتيح تصميمات مرنة حيث يمكن للكائنات (objects) أداء أدوار مختلفة:
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) الاقتران الضعيف - حيث يعتمد الكود الخاص بك على التجريدات بدلاً من التنفيذات الملموسة، مما يسهل عملية التوسيع والاختبار.
التحدي
سهللنقم ببناء نظام لإدارة الأجهزة يوضح كيف تحدد الواجهات (interfaces) العقود لأنواع مختلفة من الأجهزة. ستقوم بإنشاء واجهتين منفصلتين يمكن للأجهزة تنفيذهما، ثم بناء فئات أجهزة ملموسة (concrete classes) تفي بأحد هذين العقدين أو كليهما.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
Interfaces.h: تحديد فئتين مجردتين تماماً (واجهات) تمثلان قدرات الأجهزة المختلفة:Powerable— أي جهاز يمكن تشغيله وإيقافه:- دالة افتراضية بحتة (pure virtual) باسم
powerOn() - دالة افتراضية بحتة باسم
powerOff() - دالة افتراضية بحتة باسم
getPowerStatus()تعيدstd::string - مدمر افتراضي (virtual destructor)
Connectable— أي جهاز يمكنه الاتصال بشبكة:- دالة افتراضية بحتة باسم
connect(const std::string& network) - دالة افتراضية بحتة باسم
disconnect() - دالة افتراضية بحتة باسم
getConnectionInfo()تعيدstd::string - مدمر افتراضي (virtual destructor)
- دالة افتراضية بحتة (pure virtual) باسم
Devices.h: تنفيذ فئات الأجهزة الملموسة التي تستخدم هذه الواجهات:Lamp— تنفذ واجهةPowerableفقط:- عضو خاص
bool isOn(يبدأ بـfalse) - عضو خاص
std::string name - منشئ (constructor) يأخذ اسم المصباح
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 name)
- العلامة التجارية للتلفاز (TV brand)
- اسم الشبكة (Network name)
قم بإنشاء
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 اعتماداً على مؤشر الواجهة الذي تستخدمه. هذه المرونة هي قوة تنفيذ واجهات متعددة — حيث يمكن للكود الخاص بك العمل مع أي جهاز يفي بالعقد الذي يحتاجه، دون معرفة النوع الملموس للجهاز.
ورقة مرجعية
تُعد الواجهة (interface) في لغة C++ فئة مجردة (abstract class) تكون فيها جميع الدوال الأعضاء افتراضية بحتة (pure virtual). تُحدد الواجهات فقط العمليات التي يجب أن تكون موجودة دون أي تنفيذ (implementation):
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};تقوم الفئات (Classes) بتنفيذ الواجهات عن طريق تجاوز (overriding) جميع الدوال الافتراضية البحتة:
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; }
};يمكن للفئة تنفيذ واجهات متعددة باستخدام الوراثة المتعددة (multiple inheritance):
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; }
};تعزز الواجهات الارتباط الضعيف (loose coupling) من خلال السماح للكود بالاعتماد على التجريدات (abstractions) بدلاً من التنفيذات الملموسة (concrete implementations).
جرّب بنفسك
#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 مع اسم الشبكة
// اطبع: Connection: <connectionInfo>
// TODO: طباعة سطر فارغ
// TODO: المرور عبر مصفوفة Powerable واستدعاء ()powerOff لكل جهاز
// بعد كل ()powerOff، اطبع: Status: <powerStatus>
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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مراجعة - الحاويات العامة