نمط المحول
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 71 من 87.
يُعد نمط Adapter نمطًا هيكليًا لتصميم البرمجيات يتيح للواجهات غير المتوافقة العمل معًا. ويعمل كجسر بين فئتين، إذ يحوّل واجهة إحدى الفئتين إلى واجهة يتوقعها العميل. فكّر فيه كمحوّل طاقة يتيح لك توصيل جهاز أوروبي بمقبس أمريكي.
تخيّل أن لديك واجهة MediaPlayer موجودة تشغّل ملفات صوتية، لكنك تحتاج إلى دمج مكتبة تابعة لجهة خارجية تستخدم واجهة مختلفة تمامًا لتشغيل مقاطع الفيديو:
// الواجهة الحالية التي يستخدمها الكود الخاص بك
interface MediaPlayer {
void play(String filename);
}
// فئة من طرف ثالث ذات واجهة غير متوافقة
class AdvancedVideoPlayer {
public void playVideo(String filename) {
System.out.println("Playing video: " + filename);
}
}يقوم adapter بتغليف class الـincompatible ويستخدم implements لتطبيق الواجهة 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 ذا قيمة كبيرة عند دمج الشيفرة legacy أو مكتبات الجهات الخارجية دون تعديل شيفرتها المصدرية. يظل Client غير مدرك أنه يتعامل مع فئة خضعت للتكييف. فهو يستخدم ببساطة الواجهة التي يعرفها.
التحدي
سهللنبنِ نظامًا لتحويل درجات الحرارة باستخدام نمط Adapter! لديك تطبيق طقس موجود يعمل بدرجات حرارة بوحدة Celsius، لكنك تحتاج إلى دمج مستشعر درجة حرارة legacy لا يُخرج إلا قراءات بوحدة Fahrenheit. وبدلًا من تعديل أيٍّ من النظامين، ستنشئ adapter يصل بينهما.
ستنظّم التعليمات البرمجية عبر أربعة ملفات:
TemperatureProvider.java: عرّف الواجهة التي يتوقعها تطبيق الطقس. يجب أن تعلن عن أسلوبgetTemperatureCelsius()يُرجع قيمة double تمثّل درجة الحرارة بوحدة Celsius.FahrenheitSensor.java: يمثّل هذا المستشعر legacy ذا الواجهة غير المتوافقة. أنشئ class يحتوي على field خاص من النوع double لدرجة الحرارة بوحدة Fahrenheit، ويتم تعيينه عبر constructor. ويجب أن يحتوي على أسلوبreadFahrenheit()يُرجع قيمة Fahrenheit المخزّنة.SensorAdapter.java: أنشئ adapter يجعل مستشعر Fahrenheit متوافقًا مع تطبيق الطقس. يجب أن يقوم adapter بتنفيذTemperatureProviderولفّ كائنFahrenheitSensor. عند استدعاءgetTemperatureCelsius()، يجب أن يقرأ من المستشعر ويحوّل القيمة إلى Celsius باستخدام الصيغة:(fahrenheit - 32) * 5 / 9.Main.java: اجمع كل شيء معًا! ستتلقى input واحدًا: قراءة درجة حرارة بوحدة Fahrenheit (double).أنشئ
FahrenheitSensorباستخدام قيمة input. ثم أنشئSensorAdapterيلفّ هذا المستشعر. خزّن adapter في متغير من النوعTemperatureProviderلإثبات أن تطبيق الطقس يمكنه استخدامه من خلال الواجهة المتوقعة.اطبع النتيجة بهذا التنسيق:
Temperature: [celsius] Cحيث تُنسّق celsius إلى منزلة عشرية واحدة.
ستتلقى input واحدًا: درجة الحرارة بوحدة Fahrenheit (double).
على سبيل المثال، عند إدخال 98.6، سيكون الخرج كما يلي:
Temperature: 37.0 Cلاحظ كيف يعمل class Main بالكامل باستخدام واجهة TemperatureProvider. فهو لا يعرف أن درجة الحرارة الفعلية تأتي من مستشعر Fahrenheit تتم مواءمته خلف الكواليس. هذه هي روعة نمط Adapter!
جرّب بنفسك
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) للتنسيق
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت