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

نمط المحول

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 97 من 104.

يسمح نمط Adapter للواجهات غير المتوافقة بالعمل معًا. ويعمل كجسر بين فئتَي class، إذ يحوّل واجهة إحدى فئتَي class إلى واجهة يتوقعها العملاء. ويكون هذا مفيدًا بشكل خاص عند دمج الشيفرة legacy أو مكتبات الجهات الخارجية التي لا تتوافق مع interface نظامك.

تخيّل أن لديك class موجودًا existing تنفّذ ما تحتاج إليه، لكن interface الخاص بها لا يتطابق مع ما يتوقّعه كودك. بدلًا من تعديل أيٍّ من الجانبين، تنشئ adapter يغلّف class الموجودة existing ويترجم الاستدعاءات:

#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");
}

يحتفظ adapter بمرجع إلى LegacyPrinter ويطبّق واجهة Printer. عند استدعاء print()، يفوّض المهمة إلى printMessage(). تعمل شيفرة العميل مع واجهة Printer من دون معرفة تطبيق legacy الموجود خلفها.

استخدم Adapter عندما تريد استخدام فئة موجودة، لكن واجهتها لا تتوافق مع ما تحتاج إليه، أو عندما تحتاج إلى إنشاء فئة قابلة لإعادة الاستخدام تتعاون مع فئات ذات واجهات غير متوافقة.

challenge icon

التحدي

سهل

لنبنِ نظام Media Player Adapter يربط بين واجهات مكتبات صوتية مختلفة. تخيّل أنك تدمج مكتبة صوتية قديمة في تطبيق حديث لتشغيل الوسائط: تعمل المكتبة القديمة بشكل مثالي، لكن واجهتها لا تتطابق مع ما يتوقعه مشغّل الوسائط لديك. هنا تحديدًا يتألق نمط Adapter.

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

  • MediaPlayer.h: Define الواجهة الهدف التي يتوقعها مشغّل الوسائط الحديث.

    أنشئ فئة abstract باسم MediaPlayer تحتوي على هذه الأساليب الافتراضية الخالصة:

    • play(const std::string& filename): تشغّل الملف المحدد
    • stop(): توقف التشغيل
    • setVolume(int level): تضبط مستوى الصوت (0-100)

    ضمّن destructor افتراضيًا.

  • LegacyAudioLib.h: أنشئ مكتبة الصوت الموجودة "legacy" ذات الواجهة غير المتوافقة.

    تمثّل فئة LegacyAudioLib مكتبة قديمة تعمل بطريقة مختلفة عما يتوقعه مشغّل الوسائط لديك. ينبغي أن تحتوي على:

    • loadAudio(const std::string& path): تطبع Legacy: Loading [path]
    • startPlayback(): تطبع Legacy: Playback started
    • stopPlayback(): تطبع Legacy: Playback stopped
    • adjustVolume(double percentage): تستقبل قيمة decimal (من 0.0 إلى 1.0) وتطبع Legacy: Volume set to [percentage] (اعرض القيمة العشرية)
  • AudioAdapter.h: ابنِ الـ adapter الذي يربط بين الواجهتين.

    ينبغي لفئة AudioAdapter أن ترث من MediaPlayer وأن تحتفظ بمرجع إلى كائن من نوع LegacyAudioLib. يترجم الـ adapter الاستدعاءات من الواجهة الحديثة إلى الواجهة القديمة:

    • ينبغي لـ play() أن يستدعي كلًا من loadAudio() وstartPlayback() على مكتبة الصوت القديمة
    • ينبغي لـ stop() أن يفوّض التنفيذ إلى stopPlayback()
    • يستقبل setVolume() عددًا صحيحًا (0-100)، لكن مكتبة الصوت القديمة تتوقع قيمة decimal (من 0.0 إلى 1.0)، لذا ستحتاج إلى تحويلها
  • main.cpp: اعرض الـ adapter أثناء العمل.

    اقرأ مدخلين:

    1. اسم ملف لتشغيله (string)
    2. مستوى صوت (integer، من 0 إلى 100)

    أنشئ كائنًا من نوع LegacyAudioLib وAudioAdapter يغلّفه. ثم استخدم الـ adapter عبر واجهة 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

لاحظ كيف تعمل تعليمة client البرمجية بالكامل باستخدام واجهة MediaPlayer. فهي لا تعرف أن مكتبة legacy تنفّذ العمل الفعلي في الخلفية. يتولى الـ adapter عملية الترجمة بأكملها، بما في ذلك تحويل مستوى الصوت الصحيح إلى قيمة عشرية. هذا هو جوهر نمط Adapter: جعل الواجهات غير المتوافقة تعمل معًا دون تعديل أي من الجانبين.

جرّب بنفسك

#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++ عبر الإنترنت