نمط المحول
جزء من قسم 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) ذا قيمة كبيرة عند دمج الكود القديم أو مكتبات الطرف الثالث دون تعديل الكود المصدري الخاص بها. يظل العميل غير مدرك أنه يعمل مع فئة تمت مواءمتها—فهو ببساطة يستخدم الواجهة التي يعرفها.
التحدي
سهللنقم ببناء نظام لتحويل درجات الحرارة باستخدام نمط المحول (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) للتنسيق
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر