Menu
CoddyTech

الواجهة مقابل الفئة المجردة (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# الحديثة تسمح بالتنفيذات الافتراضية.

عند الشك، فضّل الواجهات من أجل المرونة؛ يمكنك دائمًا إضافة صنف مجرّد لاحقًا إذا أصبحت هناك حاجة إلى تنفيذ مشترك.

challenge icon

التحدي

سهل

لننشئ نظامًا لمشغّل وسائط يوضّح متى نستخدم الواجهات ومتى نستخدم الأصناف المجرّدة. ستصمم بنية تتشارك فيها أجهزة غير مترابطة سلوك التشغيل من خلال واجهة، بينما تتشارك أنواع الوسائط المترابطة تنفيذًا مشتركًا من خلال صنف مجرّد.

ستوزّع شيفرتك على أربعة ملفات:

  • 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
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C# عبر الإنترنت