Menu
Coddy logo textTech

תכנון ממשקים ב־C++

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 61 מתוך 104.

ממשק ב־C++ הוא מחלקה מופשטת שבה כל פונקציית איבר—למעט המפרק (הווירטואלי)—היא וירטואלית טהורה. בניגוד למחלקות מופשטות שעשויות להכיל מימוש כלשהו, ממשקים מגדירים רק אילו פעולות חייבות להתקיים - הם מציינים חוזה ללא כל התנהגות.

class Drawable {
public:
    virtual void draw() = 0;
    virtual void resize(double factor) = 0;
    virtual ~Drawable() = default;
};

הממשק Drawable מבטיח שלכל מחלקה שמממשת אותו יהיו המתודות 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() עם שם הרשת
    // הדפיסו: Connection: <connectionInfo>
    
    // TODO: הדפיסו שורה ריקה
    
    // TODO: עברו על המערך Powerable וקראו ל־powerOff() עבור כל מכשיר
    // לאחר כל קריאה ל־powerOff(), הדפיסו: Status: <powerStatus>
    
    return 0;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין