نمط المحول
جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 95 من 110.
يسمح نمط المحول (Adapter pattern) للواجهات غير المتوافقة بالعمل معاً. فهو يعمل كجسر بين فئتين لا يمكنهما التعاون بطريقة أخرى، حيث يقوم بتغليف فئة موجودة بواجهة جديدة يتوقعها العملاء. وهذا مفيد بشكل خاص عند دمج مكتبات الطرف الثالث أو الكود القديم (legacy code) في تطبيقك.
تخيل أن لديك نظاماً يعمل مع بيانات JSON، ولكنك تحتاج إلى دمج مكون يخرج بيانات XML فقط. بدلاً من تعديل أي من الفئتين (class)، تقوم بإنشاء محول (adapter) يترجم بينهما:
// الواجهة الحالية التي يتوقعها نظامك
abstract class JsonData {
String getJson();
}
// فئة قديمة (Legacy) ذات واجهة غير متوافقة
class XmlService {
String getXml() {
return '<data><name>John</name></data>';
}
}
// المحول (Adapter) يجعل XmlService متوافقاً مع JsonData
class XmlToJsonAdapter implements JsonData {
final XmlService _xmlService;
XmlToJsonAdapter(this._xmlService);
@override
String getJson() {
String xml = _xmlService.getXml();
// (تبسيط) JSON إلى XML تحويل الـ
return '{"name": "John"}';
}
}
void main() {
var xmlService = XmlService();
var adapter = XmlToJsonAdapter(xmlService);
print(adapter.getJson()); // {"name": "John"}
}يقوم XmlToJsonAdapter بتغليف XmlService ويقوم بتنفيذ واجهة JsonData. يعمل كود العميل مع واجهة JsonData المألوفة دون معرفة أن البيانات جاءت في الأصل من مصدر XML. يتولى المحول جميع عمليات الترجمة داخلياً.
استخدم نمط المحول (Adapter pattern) عندما تريد استخدام فئة (class) موجودة ولكن واجهتها (interface) لا تتطابق مع ما تحتاجه، أو عندما تحتاج إلى إنشاء فئة قابلة لإعادة الاستخدام تتعاون مع فئات ذات واجهات غير متوافقة.
التحدي
سهللنقم ببناء نظام محول لمشغل وسائط! تخيل أن لديك نظاماً صوتياً حديثاً يتوقع من جميع مصادر الوسائط توفير الصوت من خلال واجهة قياسية، ولكنك تحتاج إلى دمج مشغل سجلات فينيل (vinyl) قديم الطراز لديه طريقة مختلفة تماماً في إنتاج الصوت.
ستقوم بإنشاء ملفين لتنظيم تنفيذ نمط المحول (adapter pattern) الخاص بك:
media_adapter.dart: يحتوي هذا الملف على الواجهة (interface) وفئات المحول. قم بتعريف فئة مجردة (abstract class) باسمAudioSourceيتوقعها نظامك الحديث - يجب أن تحتوي على طريقةString playAudio()تعيد مخرجات الصوت كسلسلة نصية. ثم قم بإنشاء فئةVinylPlayerتمثل المعدات القديمة - تحتوي على حقلtrackNameوطريقةString spinRecord()تعيد*crackle* Playing vinyl: [trackName] *crackle*. أخيراً، قم ببناءVinylAdapterيقوم بتنفيذAudioSourceويغلفVinylPlayer. يجب أن تقوم طريقةplayAudio()الخاصة بالمحول باستدعاء طريقةspinRecord()لمشغل الفينيل وإعادة النتيجة - مما يربط بين الواجهات غير المتوافقة.main.dart: قم باستيراد ملف محول الوسائط الخاص بك ووضح كيف يجعل المحول مشغل الفينيل القديم يعمل مع الكود الذي يتوقع واجهةAudioSourceالحديثة. قم بإنشاءVinylPlayerمع اسم المسارBohemian Rhapsody. ثم قم بإنشاءVinylAdapterيغلف مشغل الفينيل هذا. قم بتخزين المحول في متغير من نوعAudioSourceلإثبات أنه يتوافق مع الواجهة المتوقعة. استدعِplayAudio()على مصدر الصوت الخاص بك واطبع النتيجة.
جمال هذا النمط هو أن الكود الأساسي الخاص بك يعمل مع واجهة AudioSource القياسية - ولا يحتاج إلى معرفة أنه خلف الكواليس، يقوم مشغل فينيل قديم بالعمل الفعلي!
المخرجات المتوقعة:
*crackle* Playing vinyl: Bohemian Rhapsody *crackle*ورقة مرجعية
يسمح نمط المحول (Adapter pattern) للواجهات غير المتوافقة بالعمل معاً من خلال العمل كجسر بين فئتين لا يمكنهما التعاون بطريقة أخرى.
يقوم المحول بتغليف فئة موجودة بواجهة جديدة يتوقعها العملاء، مما يجعله مفيداً عند دمج مكتبات خارجية أو أكواد قديمة (legacy code).
الهيكل الأساسي
// الواجهة المستهدفة التي يتوقعها العملاء
abstract class JsonData {
String getJson();
}
// فئة موجودة بواجهة غير متوافقة
class XmlService {
String getXml() {
return '<data><name>John</name></data>';
}
}
// المحول يجعل XmlService متوافقاً مع JsonData
class XmlToJsonAdapter implements JsonData {
final XmlService _xmlService;
XmlToJsonAdapter(this._xmlService);
@override
String getJson() {
String xml = _xmlService.getXml();
// تحويل XML إلى JSON
return '{"name": "John"}';
}
}الاستخدام
void main() {
var xmlService = XmlService();
var adapter = XmlToJsonAdapter(xmlService);
print(adapter.getJson()); // {"name": "John"}
}يقوم المحول بتنفيذ الواجهة المستهدفة مع تغليف الفئة غير المتوافقة. يعمل كود العميل مع الواجهة المألوفة دون معرفة التنفيذ الأساسي.
متى يُستخدم: عندما تحتاج إلى استخدام فئة موجودة ولكن واجهتها لا تتطابق مع ما تحتاجه، أو عند إنشاء فئة قابلة لإعادة الاستخدام تتعاون مع فئات ذات واجهات غير متوافقة.
جرّب بنفسك
import 'media_adapter.dart';
void main() {
// TODO: "Bohemian Rhapsody" باسم المسار VinylPlayer قم بإنشاء
// TODO: VinylPlayer الذي يغلف VinylAdapter قم بإنشاء
// TODO: AudioSource قم بتخزين المحول في متغير من نوع
// TODO: الخاصة بمصدر الصوت لديك واطبع النتيجة playAudio() قم باستدعاء
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةالمكتبات والـ Importsمقدمة في الـ OOPالـ Classes مقابل الـ Objectsالكلمة المفتاحية thisالـ Methodsمتغيرات الـ Instanceأساسيات الـ Constructorمراجعة - آلة حاسبة بسيطة4أمان الـ Null
مقدمة في أمان الـ NullNullable مقابل Non-Nullableعاملا الـ ? و !الكلمة المفتاحية Late وأمان الـ Nullالعوامل المدركة للـ Nullأمان الـ Null في الـ Classesمراجعة - نظام ملف تعريف المستخدم7الفئات المجردة والواجهات
الفئات المجردةالدوال المجردةالواجهات في Dartالواجهات الضمنيةالتنفيذ مقابل التوسيعالواجهات المتعددةمراجعة - حاسبة الأشكال10المجموعات والأنماط العامة
نظرة عامة على List و Set و Mapالمجموعات آمنة النوعالفئات العامةالدوال العامةقيود الأنماط العامةIterable و Iteratorملخص - التخزين العام13مفاهيم OOP المتقدمة
التركيب مقابل الوراثة (Composition vs Inheritance)دوال التوسعة (Extension Methods)الأصناف القابلة للاستدعاء (Callable Classes)الأصناف المختومة (Sealed Classes - Dart 3)السجلات (Records - Dart 3)الأنماط والمطابقة (Patterns & Matching - 3.0)التعدادات مع الدوال (Enums with Methods)2المنشئات في Dart
المنشئ الافتراضيالمنشئات المسماةقوائم التهيئةالمنشئات الثابتةالمنشئات المصنعيةالمنشئات الموجهةملخص - بناء الأشكال5التغليف (Encapsulation)
الأعضاء العامة مقابل الخاصةاتفاقية البادئة _الخصوصية على مستوى المكتبةتعمق في Getters و Settersإخفاء المعلوماتمراجعة - سجلات الطلاب8Mixins
مقدمة إلى Mixinsإنشاء Mixinsاستخدام Mixins متعددةالكلمة المفتاحية on في MixinsMixin مقابل الوراثة (Inheritance)Mixin مقابل الواجهة (Interface)مراجعة - نظام الحيوانات (Animal System)3خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticحقول Final و Constمتغيرات Lateالـ Static Methods والحقولدوال الـ Getters والـ Settersمراجعة - مدير الحساب البنكي6الوراثة
أساسيات الوراثةالكلمة المفتاحية superإعادة تعريف الدوال (Method Overriding)الوسم @overrideالكلمة المفتاحية final Classالدوال البانية والوراثةمراجعة - هيكلية الموظفين9تعدد الأشكال
أساسيات تعدد الأشكالتعدد الأشكال عبر الـ Interfacesالتحقق من النوع أثناء التشغيلالمعاملات is و asالكلمة المفتاحية Covariantملخص - معالج الدفع12البرمجة الكائنية غير المتزامنة (Async OOP)
Futures و async/awaitأساسيات الـ Streamsمتحكمات الـ Streamsالـ Constructors غير المتزامنةالعمليات غير المتزامنة في دوال الكلاسمراجعة - جالب البيانات (Data Fetcher)15أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع