نمط المحول
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 57 من 70.
يُعد نمط المحول (Adapter pattern) نمطاً هيكلياً يسمح للواجهات غير المتوافقة بالعمل معاً. فهو يعمل كجسر بين فئتين، حيث يقوم بتحويل واجهة فئة معينة إلى واجهة يتوقعها العملاء. فكر فيه كمحول طاقة يتيح لك توصيل جهاز أوروبي بمقبس أمريكي.
هذا النمط مفيد بشكل خاص عندما تريد استخدام فئة (class) موجودة ولكن واجهتها (interface) لا تتوافق مع ما يحتاجه الكود الخاص بك. بدلاً من تعديل الفئة الأصلية (التي قد لا تملك السيطرة عليها)، تقوم بإنشاء محول (adapter) يغلفها:
// فئة موجودة بواجهة غير متوافقة
public class OldPrinter
{
public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}
// الواجهة التي يتوقعها الكود الخاص بك
public interface IPrinter
{
void Print(string text);
}
// المحول (Adapter) يسد الفجوة
public class PrinterAdapter : IPrinter
{
private OldPrinter _oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter)
{
_oldPrinter = oldPrinter;
}
public void Print(string text)
{
_oldPrinter.PrintMessage(text); // تفويض إلى الواجهة القديمة
}
}الآن يمكن للكود الخاص بك العمل مع الطابعة القديمة من خلال الواجهة المتوقعة:
OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);
printer.Print("Hello"); // القديم: Helloيُعد نمط المحول (Adapter pattern) ذا قيمة لا تُقدر بثمن عند دمج مكتبات الطرف الثالث أو الكود القديم في تطبيقك. يعمل كود العميل مع واجهة IPrinter، وهو غير مدرك تماماً بأن فئة قديمة وغير متوافقة هي التي تقوم بالعمل الفعلي خلف الكواليس.
التحدي
سهللنقم ببناء نظام مشغل وسائط باستخدام نمط المحول (Adapter pattern). لديك مكتبة صوتية موجودة بالفعل تقوم بتشغيل ملفات MP3، ولكن تطبيقك يحتاج إلى واجهة موحدة يمكنها التعامل مع أنواع وسائط مختلفة. ستقوم بإنشاء محول لسد هذه الفجوة، مما يسمح لمشغل الوسائط الحديث الخاص بك بالعمل بسلاسة مع المكتبة الصوتية القديمة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
LegacyAudio.cs: يمثل هذا مكتبة موجودة لا يمكنك تعديلها. قم بإنشاء فئةLegacyAudioPlayerداخل مساحة الأسماءMediaSystemمع طريقةPlayMp3(string filename)التي تعيد السلسلة النصية"Playing MP3: {filename}". هذا هو "المُكيف عليه" (adaptee) - الفئة ذات الواجهة غير المتوافقة.MediaAdapter.cs: قم بإنشاء الواجهة والمحول في نفس مساحة الأسماء. حدد واجهةIMediaPlayerمع طريقةPlay(string filename)التي تعيد سلسلة نصية. ثم قم بإنشاء فئةMediaPlayerAdapterالتي تنفذIMediaPlayer. يجب أن يقوم المحول الخاص بك بتغليف مثيل منLegacyAudioPlayer(يتم تمريره عبر المنشئ) وترجمة استدعاءPlayإلى طريقةPlayMp3الخاصة بالمشغل القديم.Program.cs: اجمع كل شيء معاً عن طريق إنشاء المشغل القديم، وتغليفه في المحول الخاص بك، واستخدام المحول من خلال واجهةIMediaPlayer. يوضح هذا كيف يمكن لكود العميل العمل مع الواجهة الحديثة بينما يتولى المحول الاتصال بالنظام القديم خلف الكواليس.
ستتلقى مدخلاً واحداً:
- اسم الملف المراد تشغيله (على سبيل المثال،
song.mp3)
قم بإنشاء LegacyAudioPlayer، وقم بتغليفه في MediaPlayerAdapter، ثم استدعِ Play على المحول باستخدام واجهة IMediaPlayer واطبع النتيجة.
على سبيل المثال، إذا كان الإدخال هو favorite_track.mp3، يجب أن يكون الإخراج:
Playing MP3: favorite_track.mp3جمال هذا النمط هو أن الكود الرئيسي الخاص بك يتفاعل فقط مع IMediaPlayer - وليس لديه أي فكرة عن أن مكتبة صوتية قديمة هي التي تقوم بالعمل الفعلي. إذا احتجت لاحقاً لدعم مكتبة قديمة مختلفة، فستقوم ببساطة بإنشاء محول جديد دون تغيير أي كود للعميل!
ورقة مرجعية
يُعد نمط المحول (Adapter pattern) نمطاً هيكلياً يسمح للواجهات غير المتوافقة بالعمل معاً من خلال العمل كجسر بين فئتين.
يقوم النمط بتحويل واجهة فئة معينة إلى واجهة يتوقعها العملاء، وهو مفيد عندما تريد استخدام فئة موجودة ولكن واجهتها لا تتطابق مع احتياجات الكود الخاص بك.
الهيكل الأساسي:
// فئة موجودة بواجهة غير متوافقة (Adaptee)
public class OldPrinter
{
public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}
// الواجهة المستهدفة التي يتوقعها الكود الخاص بك
public interface IPrinter
{
void Print(string text);
}
// المحول يسد الفجوة
public class PrinterAdapter : IPrinter
{
private OldPrinter _oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter)
{
_oldPrinter = oldPrinter;
}
public void Print(string text)
{
_oldPrinter.PrintMessage(text); // يفوض العمل للواجهة القديمة
}
}الاستخدام:
OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);
printer.Print("Hello"); // Old: Helloيعمل كود العميل مع الواجهة المستهدفة، دون أن يدرك أن فئة غير متوافقة هي التي تقوم بالعمل الفعلي خلف الكواليس. هذا النمط ذو قيمة خاصة عند دمج مكتبات خارجية أو أكواد قديمة (legacy code).
جرّب بنفسك
using System;
using MediaSystem;
class Program
{
public static void Main(string[] args)
{
// قراءة مدخل اسم الملف
string filename = Console.ReadLine();
// TODO: إنشاء مثيل من LegacyAudioPlayer
// TODO: تغليفه في MediaPlayerAdapter
// TODO: استخدام المحول من خلال واجهة IMediaPlayer
// وطباعة نتيجة استدعاء Play مع filename
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection