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

نمط المحول

جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 71 من 87.

يُعد نمط المحول (Adapter Pattern) نمط تصميم هيكلياً يسمح للواجهات غير المتوافقة بالعمل معاً. فهو يعمل كجسر بين فئتين (classes)، حيث يقوم بتحويل واجهة فئة واحدة إلى واجهة يتوقعها العميل (client). فكر فيه كمحول طاقة يتيح لك توصيل جهاز أوروبي بمقبس أمريكي.

تخيل أن لديك واجهة MediaPlayer موجودة تقوم بتشغيل ملفات الصوت، ولكنك تحتاج إلى دمج مكتبة خارجية تستخدم واجهة مختلفة تماماً لتشغيل مقاطع الفيديو:

// الواجهة الحالية التي يستخدمها الكود الخاص بك
interface MediaPlayer {
    void play(String filename);
}

// فئة من طرف ثالث بواجهة غير متوافقة
class AdvancedVideoPlayer {
    public void playVideo(String filename) {
        System.out.println("Playing video: " + filename);
    }
}

يقوم المحول (adapter) بتغليف الفئة (class) غير المتوافقة وينفذ الواجهة (interface) المتوقعة، مترجماً الاستدعاءات بينهما:

class VideoPlayerAdapter implements MediaPlayer {
    private AdvancedVideoPlayer videoPlayer;
    
    public VideoPlayerAdapter() {
        this.videoPlayer = new AdvancedVideoPlayer();
    }
    
    public void play(String filename) {
        videoPlayer.playVideo(filename);
    }
}

الآن يمكن لكود العميل الخاص بك استخدام مشغل الفيديو من خلال واجهة MediaPlayer المألوفة:

MediaPlayer player = new VideoPlayerAdapter();
player.play("movie.mp4");  // المخرجات: تشغيل الفيديو: movie.mp4

يعد نمط المحول (Adapter Pattern) ذا قيمة كبيرة عند دمج الكود القديم أو مكتبات الطرف الثالث دون تعديل الكود المصدري الخاص بها. يظل العميل غير مدرك أنه يعمل مع فئة تمت مواءمتها—فهو ببساطة يستخدم الواجهة التي يعرفها.

challenge icon

التحدي

سهل

لنقم ببناء نظام لتحويل درجات الحرارة باستخدام نمط المحول (Adapter Pattern)! لديك تطبيق طقس حالي يعمل مع درجات الحرارة المئوية (Celsius)، ولكنك تحتاج إلى دمج مستشعر حرارة قديم لا يعطي القراءات إلا بالفهرنهايت (Fahrenheit). بدلاً من تعديل أي من النظامين، ستقوم بإنشاء محول يسد هذه الفجوة.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • TemperatureProvider.java: قم بتعريف الواجهة (interface) التي يتوقعها تطبيق الطقس الخاص بك. يجب أن تعلن عن طريقة getTemperatureCelsius() التي تعيد قيمة من نوع double تمثل درجة الحرارة بالدرجة المئوية.
  • FahrenheitSensor.java: يمثل هذا المستشعر القديم ذو الواجهة غير المتوافقة. قم بإنشاء فئة تحتوي على حقل خاص (private double) لدرجة الحرارة بالفهرنهايت، يتم تعيينه من خلال المنشئ (constructor). يجب أن تحتوي على طريقة readFahrenheit() تعيد قيمة الفهرنهايت المخزنة.
  • SensorAdapter.java: قم بإنشاء المحول الذي يجعل مستشعر الفهرنهايت متوافقاً مع تطبيق الطقس الخاص بك. يجب أن يقوم المحول الخاص بك بتنفيذ TemperatureProvider وتغليف FahrenheitSensor. عندما يتم استدعاء getTemperatureCelsius()، يجب أن يقرأ من المستشعر ويحول القيمة إلى مئوية باستخدام الصيغة: (fahrenheit - 32) * 5 / 9.
  • Main.java: اجمع كل شيء معاً! ستتلقى مدخلاً واحداً: قراءة درجة الحرارة بالفهرنهايت (double).

    قم بإنشاء FahrenheitSensor باستخدام القيمة المدخلة. ثم قم بإنشاء SensorAdapter يغلف هذا المستشعر. قم بتخزين المحول في متغير من نوع TemperatureProvider لإثبات أن تطبيق الطقس الخاص بك يمكنه استخدامه من خلال الواجهة المتوقعة.

    اطبع النتيجة بهذا التنسيق: Temperature: [celsius] C حيث يتم تنسيق celsius إلى منزلة عشرية واحدة.

ستتلقى مدخلاً واحداً: درجة الحرارة بالفهرنهايت (double).

على سبيل المثال، مع المدخل 98.6، سيكون مخرجك هو:

Temperature: 37.0 C

لاحظ كيف تعمل فئة Main الخاصة بك تماماً مع واجهة TemperatureProvider — ليس لديها أي فكرة عن أن درجة الحرارة الفعلية تأتي من مستشعر فهرنهايت يتم تكييفه خلف الكواليس. هذا هو جمال نمط المحول (Adapter Pattern)!

ورقة مرجعية

يُعد نمط المحول (Adapter Pattern) نمط تصميم هيكلي يسمح للواجهات غير المتوافقة بالعمل معاً من خلال العمل كجسر بين فئتين، حيث يقوم بتحويل واجهة واحدة إلى واجهة أخرى يتوقعها العميل.

المكونات الرئيسية

  • الواجهة المستهدفة (Target Interface): الواجهة التي يتوقع العميل استخدامها
  • المحول إليه (Adaptee): الفئة الموجودة حالياً ذات الواجهة غير المتوافقة
  • المحول (Adapter): الفئة التي تنفذ الواجهة المستهدفة وتغلف المحول إليه، وتقوم بترجمة الاستدعاءات بينهما

الهيكل الأساسي

// الواجهة المستهدفة (Target interface)
interface MediaPlayer {
    void play(String filename);
}

// المحول إليه (فئة غير متوافقة)
class AdvancedVideoPlayer {
    public void playVideo(String filename) {
        System.out.println("Playing video: " + filename);
    }
}

// المحول (Adapter)
class VideoPlayerAdapter implements MediaPlayer {
    private AdvancedVideoPlayer videoPlayer;
    
    public VideoPlayerAdapter() {
        this.videoPlayer = new AdvancedVideoPlayer();
    }
    
    public void play(String filename) {
        videoPlayer.playVideo(filename);
    }
}

// استخدام العميل
MediaPlayer player = new VideoPlayerAdapter();
player.play("movie.mp4");  // Output: Playing video: movie.mp4

متى يُستخدم

يكون نمط المحول مفيداً عندما:

  • دمج التعليمات البرمجية القديمة (legacy code) أو مكتبات الطرف الثالث دون تعديل الكود المصدري الخاص بها
  • جعل الواجهات غير المتوافقة تعمل معاً
  • حاجة العميل إلى البقاء غير مدرك للتنفيذ الذي تم تكييفه

جرّب بنفسك

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double fahrenheit = scanner.nextDouble();
        
        // TODO: قم بإنشاء FahrenheitSensor باستخدام القيمة المدخلة
        
        // TODO: قم بإنشاء SensorAdapter الذي يغلف المستشعر (sensor)
        
        // TODO: قم بتخزين المحول (adapter) في متغير من نوع TemperatureProvider
        
        // TODO: احصل على درجة الحرارة بالسيليزي وقم بطباعتها
        // التنسيق: "Temperature: [celsius] C" مع منزلة عشرية واحدة
        // تلميح: استخدم String.format("%.1f", value) للتنسيق
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming