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

تصميم الواجهات (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; }
};

تُعزّز الواجهات الاقترانَ الضعيف — إذ يعتمد كودك على التجريدات بدلًا من التطبيقات الملموسة، مما يجعل توسيعه واختباره أسهل.

challenge icon

التحدي

سهل

لنَبْنِ نظامًا لإدارة الأجهزة يوضّح كيفية تعريف الواجهات لعقود لأنواع مختلفة من الأجهزة. ستنشئ واجهتين منفصلتين يمكن للأجهزة تنفيذهما، ثم تبني فئات أجهزة ملموسة تفي بأحد هذين العقدين أو كليهما.

ستنظّم التعليمات البرمجية عبر ثلاثة ملفات:

  • 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 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. اسم المصباح
    2. العلامة التجارية للتلفاز
    3. اسم الشبكة

    أنشئ كائنًا من 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;
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت