תכנון ממשקים ב־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; }
};ממשקים מעודדים צימוד רופף — הקוד שלך תלוי בהפשטות ולא במימושים קונקרטיים, וכך קל יותר להרחיב ולבדוק אותו.
אתגר
קלבואו נבנה מערכת לניהול מכשירים שמדגימה כיצד ממשקים מגדירים חוזים עבור סוגים שונים של מכשירים. תיצרו שני ממשקים נפרדים שמכשירים יכולים לממש, ואז תבנו מחלקות קונקרטיות של מכשירים שמקיימות אחד מהחוזים האלה או את שניהם.
תארגנו את הקוד שלכם בשלושה קבצים:
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() עם שם הרשת
// הדפיסו: Connection: <connectionInfo>
// TODO: הדפיסו שורה ריקה
// TODO: עברו על המערך Powerable וקראו ל־powerOff() עבור כל מכשיר
// לאחר כל קריאה ל־powerOff(), הדפיסו: Status: <powerStatus>
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין