Menu
Coddy logo textTech

תבנית המתאם

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 כשאתם רוצים להשתמש במחלקה קיימת, אך הממשק שלה אינו תואם למה שאתם צריכים, או כשאתם צריכים ליצור מחלקה לשימוש חוזר שמשתפת פעולה עם מחלקות בעלות ממשקים שאינם תואמים.

challenge icon

אתגר

קל

בואו נבנה מערכת מתאם לנגן מדיה שמגשרת בין ממשקים של ספריות שמע שונות. דמיינו שאתם משלבים ספריית שמע ותיקה באפליקציית נגן מדיה מודרנית: הספרייה הישנה פועלת בצורה מושלמת, אבל הממשק שלה לא תואם למה שהנגן שלכם מצפה לו. בדיוק כאן תבנית המתאם באה לידי ביטוי.

תארגנו את הקוד שלכם בארבעה קבצים:

  • 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 started
    • stopPlayback(): מדפיסה Legacy: Playback stopped
    • adjustVolume(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: הדגימו את פעולת המתאם.

    קראו שתי קלטים:

    1. שם קובץ להשמעה (מחרוזת)
    2. רמת עוצמת קול (מספר שלם, 0-100)

    צרו מופע של LegacyAudioLib ומתאם AudioAdapter שעוטף אותו. לאחר מכן השתמשו במתאם דרך הממשק MediaPlayer (אחסנו אותו כמצביע MediaPlayer) כדי:

    1. להגדיר את עוצמת הקול לרמת הקלט
    2. להשמיע את קובץ הקלט
    3. לעצור את ההפעלה

לדוגמה, עבור הקלטים 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;
}
quiz iconבחנו את עצמכם

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

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

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