الفرق بين الواجهات Interfaces وبين الفئات المجردة Abstract Classes

الفئات المجردة Abstract Classes

الفئات المجردة Abstract Classes هي تمثيل مجرد لنوع معين، ومن مقتضيات هذا التمثيل ألا يكون له وجود واقعيا إذ يبقى في جيز التجريد والاصطلاح فقط، مما يترتب على هذا المعنى من إلغاء لعملية الاستنساخ  Instanciation، إذ من المعلوم أن الفئات المجردة في البرمجة الكائنية التوجه لا تسمح باستنساخ كائنات منها ويبقى دورها محصورا في عملية تعريف العناصر الأساسية للفئات بغرض توريثها إلى الفئات المشتقة.
ويمكن للفئة المجردة أن تشتمل على وظائف مجردة abstract methods قابلة لإعادة التعريف على مستوى الفئات المشتقة، كما يمكنها أيضا ألا تشتمل على أية وظيفة مجردة، والوظائف المجردة هي وظائف لا تحتوي على أية أوامر برمجية Implementation، بل تعرض فقط تعريف الوظيفة أو ما يعرف بالتوقيع Signature كما يوضح المثال التالي:

    abstract class Person
    {
        string _name;
        public Person (string name)
        {
            _name = name;
        }
        abstract public void Work();
    }
الفئة Person فئة مجردة وعلامة ذلك أمر التعريف abstract الذي تقدمها، وهي تحتوي على حقل من نوع نصي اسمه _name يأخذ قيمته في مشيد الفئة Constructor (علما أن المشيد في الفئات المجردة لا معنى له، بيد أن دور المشيد هو تشييد الكائنات في زمن الاستنساخ، ومادام الاستنساخ غير ممكن من الفئات المجردة فلا قيمة لوضع المشيد هنا، لكن يتم وضعه لتحديد قيم بدئية للحقول المشتركة بين جميع الفئات المشتقة التي ترث من الفئة المجردة، ويمكن تغيير قيمة الحقل في المشيدات الفرعية عبر الكلمة base  كما سيأتي معنا).
بعد ذلك قمنا بتعريف وظيفة مجردة abstract method أسميناها Work، هاته الوظيفة سنقوم بإعادة تعريفها في الفئات المشتقة عبر استخدام الأمر Override كما يعرض الكود التالي:
    class Programmer:Person
    {
        string _speciality;

        public Programmer(string speciality, string name) : base(name)
        {
            _speciality = speciality;
        }

        public override void Work()
        {
            Console.WriteLine("I am Programmer :) ");
        }
    }
في الكود أعلاه، قمنا بإنشاء فئة اسمها Programmer مشتقة من الفئة الرئيسية المجردة Person، داخل هاته الفئة المشتقة قمنا بإسناد قيمة بدئية للحقل _name من جديد عبر الأمر base الذي يحيل إلى الفئة الأصلية، كما قمنا بإعطاء قيمة بدئية لحقل جديد أسميناه Speciality.
ثم في الجزء الأهم، قمنا بإعادة تعريف الوظيفة المجردة Work من خلال الأمر Override، وبالتالي عند استدعاء هاته الوظيفة عبر كائن مستنسخ من الفئة Programmer  سيتم تنفيذ الكود الموافق لها، كما يوضح الكود التالي:
class TestProg
    {
        static void main()
        {
            //Create new instance from Programmer Class
            Programmer programmer = new Programmer("Desktop", "Khalid ESSAADANI");

            //Call Work method from derived class
            programmer.Work(); //Print: I am Programmer
        }
    }
إذن كخلاصة، فالفئات المجردة هي عبارة عن فئات تقوم بتعريف العناصر العامة لنوع معين، ولا تسمح بعملية الاستنساخ المباشر منها بل تسمح بذلك فقط عبر آلية التوريث /  الاشتقاق Inheritance بحيث يمكننا استنساخ كائنات فقط من الفئات الفرعية المشتقة من هذه الفئات المجردة.

الواجهات Interfaces:

تقوم الواجهة Interface بتعريف الوظائف التي على الفئات - التي ستقوم بتطبيقها – إعادة تعريفها، وكما هو الحال في الوظائف المجردة فإن الواجهات لا تحتوي على أية أوامر برمجية Implementation، بل تحتوي فقط على تعريف لتوقيعات الوظائف الواجب إعادة تعريفها على مستوى الفئات التي ستستخدم هاته الواجهة.
ولعل سؤالا ملحا ينخر في ذهنك الآن: إذن لماذا تصلح الواجهات مادامت تؤدي تقريبا نفس الدور الذي تؤديه الفئات المجردة؟
سؤال وجيه و حق له أن يطرح، وكجواب مبدئي يمكننا القول، أن الواجهات في اللغات الحديثة مثل سي شارب وجافا وفيجوال بيسك دوت نيت، أتت كحل حاسم لأزمة الوراثة المتعددة Multiple inheritance، إذ من المعلوم أن هذه اللغات الثلاثة التي ذكرناها لا تسمح بالوراثة المتعددة، بمعنى أن فئة معينة لا تستطيع أن ترث من أكثر من فئة رئيسية، بيد أن التسلسل المسموح به هو فئة فرعية ترث من فئة رئيسية واحدة.
فلما كان الأمر كذلك، قامت هذه اللغات بتقديم بديل للوراثة المتعددة متمثلا في الواجهات، حيث صار للفئة الواحدة أن تطبق واجهة أو عدة واجهات في الوقت عينه وبالتالي يكفي أن ننشئ الواجهات التي سنحتاجها ونضع فيها ما نشاء من تعريفات للوظائف Specification، شريطة ألا يتعدى التعريف حدود التوقيع Signature لأن الواجهات لا تحتوي على أوامر برمجية Implementation، ثم بعد ذلك نأتي إلى الفئة المستهدفة ونعيد تعريف عناصر الواجهات عليها بكيفية قريبة جدا من طريقة الوراثة، حيث في لغة الفيجوال بيسك دوت نيت نقوم بتطبيق الواجهة في فئة ما من خلال الكلمة المحجوزة Implements وكذلك في لغة جافا تتم عملية التطبيق عبر استخدام الكلمة المحجوزة implements، بينما في لغة سي شارب، يبقى رمز تطبيق الواجهات هو نفسه رمز الوراثة (نقطتان هكذا : ).
وهذا مثال على إنشاء واجهة في لغة سي شارب:
public interface IVehicle
    {
        string Model { get; set; }
        DateTime ManuFacturedDate { get; set; }
        byte Age();
    }
الواجهة أعلاه اسمها IVehicle / المركبات، تحتوي على تعريف لخاصيتين وهما موديل المركبة Model و تاريخ التصنيع ManuFacturedDate، وعلى وظيفة اسمها Age، لو لاحظت معي فإننا اكتفنيا في تعريف الخصائص بذكر get و set  فقط دون تحديد التفاصيل، لأن الواجهات كما ذكرنا تشمل التعريف Specification وليس الكود Implementation.
إذا أردنا تطبيق هاته الواجهة في فئة ما، فالمسألة بسيطة وصيغتها كما تقدم شبيهة بصيغة الوراثة أي كما يلي:
    class Car : IVehicle
    {
        public string Model { get; set; }
        public DateTime ManuFacturedDate { get; set; }


        public byte Age()
        {
            return (byte)(DateTime.Now.Year - ManuFacturedDate.Year);
        }
    }
جدير بالذكر أن تطبيق واجهة ما في إحدى الفئات يستلزم إعادة تعريف جميع العناصر التي قامت الواجهة بتعريفها، كما هو الحال في المثال أعلاه، فقد قمنا بتعريف الخصائص Model و ManuFacturedDate وكذلك الوظيفة Age التي تسمح لنا بالحصول على عمر السيارة من خلال طرح تاريخ التصنيع من التاريخ الحالي سنويا.
ويمكن للفئة الواحدة أن تطبق عدة واجهات كما يعرض المثال الآتي:
public interface IVehicle
    {
        string Model { get; set; }
        DateTime ManuFacturedDate { get; set; }
        byte Age();
    }

    public interface IColor
    {
        string Color { get; set; }

        bool CompareColors(string color);
    }

    class Car : IVehicle, IColor
    {
        //IVehicle
        public string Model { get; set; }
        public DateTime ManuFacturedDate { get; set; }


        public byte Age()
        {
            return (byte)(DateTime.Now.Year - ManuFacturedDate.Year);
        }


        //IColor
        public string Color { get; set; }

        public bool CompareColors(string color)
        {
            if (Color.Equals(color))
                return true;
            else return false;
        }


    }

في المثال أعلاه، قامت الفئة Car بتطبيق واجهتين هما IVehicle و IColor، وأعادت تعريف جميع عناصرهما.
كخلاصة، الواجهات تقوم بتعريف وظائف وخصائص الفئات فقط دون كتابة الأوامر البرمجية، والغرض من استخدام الواجهات أساسا هو تعويض مفهوم الوراثة المتعددة، إذ لا يمكن للفئة الواحدة الوراثة من أكثر من فئة رئيسية، بينما يمكنها تطبيق أكثر من واجهة.
كما تسمح الواجهات بتوصيف شامل لجميع عناصر الفئات التي تستخدمها، إذ تحتوي على تعريف جميع الوظائف والخصائص.

الفروق بين الفئات المجردة والواجهات:

1. الفئات المجردة يمكنها أن تحتوي على وظائف بأوامر برمجية Implementation، بينما الواجهات لا تسمح بذلك وتكتفي فقط بتقديم تعريفات للعناصر.
2. يمكن للفئات المجردة أن تحتوي على ما تحتويه الفئات العادية كالمشيدات Constructors  والمهدمات Destructors بينما بنية الواجهات لا تسمح بذلك.
3. يمكن للفئة الواحدة أن ترث من فئة مجردة واحدة، بينما يمكنها أن تطبق أكثر من واجهة.
4. الفئات المجردة تبدأ بالكلمة abstract بينما تبدأ الواجهات بالكلمة interface.
5. على الفئة التي تستخدم واجهة ما، إعادة تعريف جميع عناصرها، بينما في حال الوراثة من فئة مجردة فهي ليست ملزمة بذلك.
6. يستحسن استخدام الفئات المجردة حينما نحتاج إلى إعادة تعريف بعض عناصر الفئة الرئيسية فقط على مستوى الفئات البنات، بينما يستحسن استخدام الواجهات حينما نحتاج إلى إعادة تعريف جميع العناصر على مستوى الفئة.

هناك تعليقان (2):

  1. هل يمكننا الحصول على هذا المقال في شكل ملف للقراءة

    ردحذف
  2. رائع و دا موقعي و شكرا للزيارة و اعطاء رأيك
    www.network-programmers.xyz

    ردحذف