الواجهة مقابل الفئة المجردة (Abstract Class)
جزء من قسم البرمجة كائنية التوجه في رحلة C# على Coddy. الدرس 26 من 70.
تتيح لك كلٌّ من الواجهات والفئات المجرّدة تحديد عقود يجب على الفئات الأخرى الالتزام بها، لكنها تخدم أغراضًا مختلفة وتتمتع بإمكانات متميزة.
تحدّد الواجهة ما يجب على الصنف فعله، من دون أي تنفيذ. إنها عقدٌ بحت:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}يمكن أن يوفّر الصنف المجرّد كلاً من العقد والتنفيذ المشترك. إنه مخطط جزئي:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // يجب تنفيذها
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}تتمثل الاختلافات الرئيسية في قواعد الوراثة والغرض.
يمكن للفئة تنفيذ عدة واجهات، لكنها لا ترث إلا من فئة مجردة واحدة.
يمكن للفئة أيضًا أن ترث من فئة مجردة وتنفذ واجهات في الوقت نفسه: بعد النقطتين، تأتي الفئة الأساسية أولًا، ثم الواجهات، مفصولة بفواصل:
public class Airplane : Vehicle, IFlyable
{
public override void Drive()
{
Console.WriteLine("Taxiing to the runway");
}
public void Fly()
{
Console.WriteLine("Flying above the clouds");
}
}استخدم واجهة عندما تحتاج أصناف غير مترابطة إلى مشاركة سلوك (مثل IFlyable للطيور والطائرات). استخدم صنفًا مجردًا عندما تشترك أصناف مترابطة في شيفرة وحالة مشتركتين (مثل المركبات التي تشترك في آلية تشغيل المحرك).
يمكن أن تحتوي الأصناف المجرّدة على مُنشئات وحقول ومُعدِّلات وصول للأعضاء. تقليديًا، لم يكن ذلك ممكنًا في الواجهات، مع أن C# الحديثة تسمح بالتنفيذات الافتراضية.
عند الشك، فضّل الواجهات من أجل المرونة؛ يمكنك دائمًا إضافة صنف مجرّد لاحقًا إذا أصبحت هناك حاجة إلى تنفيذ مشترك.
التحدي
سهللننشئ نظامًا لمشغّل وسائط يوضّح متى نستخدم الواجهات ومتى نستخدم الأصناف المجرّدة. ستصمم بنية تتشارك فيها أجهزة غير مترابطة سلوك التشغيل من خلال واجهة، بينما تتشارك أنواع الوسائط المترابطة تنفيذًا مشتركًا من خلال صنف مجرّد.
ستوزّع شيفرتك على أربعة ملفات:
IPlayable.cs: عرّف واجهة باسمIPlayableفي مساحة الأسماءMedia. تمثّل هذه الواجهة عقدًا لأي شيء يمكنه تشغيل الوسائط، سواء أكان هاتفًا أم تلفازًا أم مشغّل موسيقى مخصّصًا. يجب أن تعلن عن طريقة واحدةPlay()تُرجع سلسلة نصية.MediaFile.cs: عرّف صنفًا مجرّدًا باسمMediaFileفي مساحة الأسماءMedia. يمثّل هذا الأساس المشترك لجميع ملفات الوسائط. يجب أن يتضمن خاصيةTitle(من النوع string) وخاصيةDuration(من النوع int، وتمثّل عدد الثواني). أضف مُنشئًا يعيّن القيمتين. أضف طريقة فعلية باسمGetInfo()تُرجع"{Title} ({Duration}s)". وأعلن أيضًا عن طريقة مجرّدةGetType()تُرجع سلسلة نصية، إذ سيحدّد كل نوع من الوسائط نوع الملف الذي يمثّله.AudioFile.cs: عرّف صنفًا باسمAudioFileفي مساحة الأسماءMediaيرث منMediaFileوينفّذIPlayable. أضف خاصيةArtist(من النوع string). يجب أن يقبل المُنشئ العنوان والمدة والفنان. أعد تعريفGetType()لإرجاع"Audio". ونفّذPlay()لإرجاع"Playing audio: {Title} by {Artist}".Program.cs: في ملفك الرئيسي، أنشئ كائنًا منAudioFileباستخدام قيم الإدخال. وضّح كلا جانبي الصنف: استدعِGetInfo()وGetType()(الموروثتين من الصنف المجرّد)، ثم استدعِPlay()(من الواجهة). خزّن ملف الصوت في متغير من النوعIPlayableواستدعِPlay()مرة أخرى لإظهار تعدد الأشكال عبر الواجهة.
ستتلقى ثلاثة مدخلات:
- عنوان الأغنية
- المدة بالثواني
- اسم الفنان
اطبع الناتج بهذا التنسيق:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}على سبيل المثال، إذا كانت المدخلات هي Bohemian Rhapsody و354 وQueen، فيجب أن يكون الناتج:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by Queenلاحظ كيف يستفيد AudioFile من كلا النهجين: فهو يرث التنفيذ المشترك (GetInfo()) من الصنف المجرّد، وفي الوقت نفسه يفي بعقد IPlayable. يوفّر الصنف المجرّد إعادة استخدام الشيفرة لأنواع الوسائط المترابطة، بينما تتيح الواجهة لأي صنف غير مترابط أن يكون «قابلًا للتشغيل» من دون مشاركة تسلسل هرمي للوراثة.
جرّب بنفسك
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// اقرأ قيم الإدخال
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: أنشئ AudioFile باستخدام قيم الإدخال
// TODO: اطبع Info: متبوعةً بنتيجة GetInfo()
// TODO: اطبع Type: متبوعةً بنتيجة GetType()
// TODO: اطبع Direct: متبوعةً بنتيجة Play()
// TODO: خزّن الملف الصوتي في متغير من النوع IPlayable
// TODO: اطبع Via Interface: متبوعةً بنتيجة Play() من متغير IPlayable
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم C# عبر الإنترنت