תבנית המתאם
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 97 מתוך 104.
תבנית המתאם מאפשרת לממשקים שאינם תואמים לעבוד יחד. היא משמשת גשר בין שתי מחלקות, וממירה את הממשק של מחלקה אחת לממשק שהלקוחות מצפים לו. הדבר שימושי במיוחד בעת שילוב של קוד מדור קודם או ספריות צד שלישי שאינן תואמות לממשק של המערכת שלך.
דמיין שיש לך מחלקה קיימת שעושה את מה שאתה צריך, אבל הממשק שלה לא תואם את מה שהקוד שלך מצפה לו. במקום לשנות את אחד הצדדים, אתה יוצר מתאם שעוטף את המחלקה הקיימת ומתרגם קריאות:
#include <iostream>
#include <string>
// מחלקה קיימת עם ממשק לא תואם
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// ממשק היעד שהלקוח מצפה לו
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// המתאם עוטף את LegacyPrinter ומממש את הממשק Printer
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // תרגם את הקריאה
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// הלקוח משתמש בממשק המצופה
adapter.print("Hello World");
}המתאם מחזיק הפניה ל־LegacyPrinter ומממש את הממשק Printer. כשקוראים ל־print(), הוא מעביר את הקריאה אל printMessage(). קוד הלקוח עובד עם הממשק Printer בלי לדעת על המימוש הישן שמתחתיו.
השתמשו בתבנית Adapter כשאתם רוצים להשתמש במחלקה קיימת, אך הממשק שלה אינו תואם למה שאתם צריכים, או כשאתם צריכים ליצור מחלקה לשימוש חוזר שמשתפת פעולה עם מחלקות בעלות ממשקים שאינם תואמים.
אתגר
קלבואו נבנה מערכת מתאם לנגן מדיה שמגשרת בין ממשקים של ספריות שמע שונות. דמיינו שאתם משלבים ספריית שמע ותיקה באפליקציית נגן מדיה מודרנית: הספרייה הישנה פועלת בצורה מושלמת, אבל הממשק שלה לא תואם למה שהנגן שלכם מצפה לו. בדיוק כאן תבנית המתאם באה לידי ביטוי.
תארגנו את הקוד שלכם בארבעה קבצים:
MediaPlayer.h: הגדירו את ממשק היעד שנגן המדיה המודרני שלכם מצפה לו.צרו מחלקה מופשטת
MediaPlayerעם המתודות הווירטואליות הטהורות האלה:play(const std::string& filename): מנגנת את הקובץ שצויןstop(): עוצרת את ההפעלהsetVolume(int level): מגדירה את עוצמת הקול (0-100)
כללו מפרק וירטואלי.
LegacyAudioLib.h: צרו את ספריית השמע הקיימת מהדור הקודם, עם ממשק שאינו תואם.המחלקה שלכם
LegacyAudioLibמייצגת ספרייה ישנה שפועלת בצורה שונה ממה שהנגן שלכם מצפה לו. היא צריכה לכלול:loadAudio(const std::string& path): מדפיסהLegacy: Loading [path]startPlayback(): מדפיסהLegacy: Playback startedstopPlayback(): מדפיסהLegacy: Playback stoppedadjustVolume(double percentage): מקבלת מספר עשרוני (מ-0.0 עד 1.0) ומדפיסהLegacy: Volume set to [percentage](מציגה את הערך העשרוני)
AudioAdapter.h: בנו את המתאם שמגשר בין שני הממשקים.המחלקה שלכם
AudioAdapterצריכה לרשת מ-MediaPlayerולהחזיק הפניה לאובייקטLegacyAudioLib. המתאם מתרגם קריאות מהממשק המודרני לממשק הישן:play()צריכה לקרוא גם ל-loadAudio()וגם ל-startPlayback()בספרייה הישנהstop()צריכה להעביר את הקריאה ל-stopPlayback()setVolume()מקבלת מספר שלם (0-100), אבל הספרייה הישנה מצפה למספר עשרוני (0.0-1.0), ולכן תצטרכו להמיר אותו
main.cpp: הדגימו את פעולת המתאם.קראו שתי קלטים:
- שם קובץ להשמעה (מחרוזת)
- רמת עוצמת קול (מספר שלם, 0-100)
צרו מופע של
LegacyAudioLibומתאםAudioAdapterשעוטף אותו. לאחר מכן השתמשו במתאם דרך הממשקMediaPlayer(אחסנו אותו כמצביעMediaPlayer) כדי:- להגדיר את עוצמת הקול לרמת הקלט
- להשמיע את קובץ הקלט
- לעצור את ההפעלה
לדוגמה, עבור הקלטים song.mp3 ו-75:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedעבור הקלטים podcast.wav ו-50:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedשימו לב שקוד הלקוח עובד כולו עם הממשק MediaPlayer. הוא אינו יודע שספרייה ישנה מבצעת את העבודה בפועל מאחורי הקלעים. המתאם מטפל בכל התרגום, כולל המרת עוצמת הקול ממספר שלם למספר עשרוני. זו מהות תבנית המתאם: לאפשר לממשקים שאינם תואמים לעבוד יחד בלי לשנות אף אחד מהצדדים.
נסו בעצמכם
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// קראו את הקלטים
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: צרו מופע של LegacyAudioLib
// TODO: צרו AudioAdapter שעוטף את הספרייה הישנה
// TODO: שמרו את המתאם כמצביע ל-MediaPlayer
// TODO: השתמשו בממשק MediaPlayer כדי:
// 1. הגדירו את עוצמת הקול לרמה שהתקבלה בקלט
// 2. נגנו את הקובץ ששמו התקבל בקלט
// 3. עצרו את הניגון
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סיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית השיטה התבניתיתתבנית המצבתבנית המורכבRAII כתבנית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין