Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

تصميم الواجهات (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) الاقتران الضعيف - حيث يعتمد الكود الخاص بك على التجريدات بدلاً من التنفيذات الملموسة، مما يسهل عملية التوسيع والاختبار.

challenge icon

التحدي

سهل

لنقم ببناء نظام لإدارة الأجهزة يوضح كيف تحدد الواجهات (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)
  • Devices.h: تنفيذ فئات الأجهزة الملموسة التي تستخدم هذه الواجهات:

    Lamp — تنفذ واجهة Powerable فقط:

    • عضو خاص bool isOn (يبدأ بـ false)
    • عضو خاص std::string name
    • منشئ (constructor) يأخذ اسم المصباح
    • powerOn() تجعل isOn تساوي true وتطبع: <name>: Light turned on
    • powerOff() تجعل isOn تساوي false وتطبع: <name>: Light turned off
    • getPowerStatus() تعيد "ON" أو "OFF" بناءً على الحالة

    SmartTV — تنفذ كلاً من Powerable و Connectable:

    • أعضاء خاصة: bool isOn (تبدأ بـ false)، std::string brand، std::string currentNetwork (تبدأ فارغة)
    • منشئ يأخذ العلامة التجارية للتلفاز
    • powerOn() تجعل isOn تساوي true وتطبع: <brand> TV: Powered on
    • powerOff() تجعل isOn تساوي false، وتمسح الشبكة، وتطبع: <brand> TV: Powered off
    • getPowerStatus() تعيد "ON" أو "OFF"
    • connect() تخزن اسم الشبكة وتطبع: <brand> TV: Connected to <network>
    • disconnect() تمسح الشبكة وتطبع: <brand> TV: Disconnected
    • getConnectionInfo() تعيد "Connected to <network>" إذا كان متصلاً، أو "Not connected" إذا كانت فارغة
  • main.cpp: قراءة ثلاثة مدخلات (كل منها في سطر منفصل):
    1. اسم المصباح (Lamp name)
    2. العلامة التجارية للتلفاز (TV brand)
    3. اسم الشبكة (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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming