JavaRush /مدونة جافا /Random-AR /تعدد الأشكال في جاوة

تعدد الأشكال في جاوة

نشرت في المجموعة
تعد الأسئلة حول OOP جزءًا لا يتجزأ من المقابلة الفنية لمنصب مطور Java في شركة تكنولوجيا المعلومات. في هذه المقالة سنتحدث عن أحد مبادئ OOP - تعدد الأشكال. سنركز على الجوانب التي يتم طرحها غالبًا أثناء المقابلات، وسنقدم أيضًا أمثلة صغيرة للتوضيح.

ما هو تعدد الأشكال؟

تعدد الأشكال هو قدرة البرنامج على استخدام الكائنات بنفس الواجهة دون معلومات حول النوع المحدد لهذا الكائن. إذا أجبت على السؤال ما هو تعدد الأشكال بهذه الطريقة، فمن المرجح أن يطلب منك شرح ما تعنيه. مرة أخرى، دون طرح مجموعة من الأسئلة الإضافية، قم بترتيب كل شيء للمقابلة.

تعدد الأشكال في جافا في مقابلة - 1
يمكننا أن نبدأ بحقيقة أن نهج OOP يتضمن بناء برنامج Java يعتمد على تفاعل الكائنات القائمة على الفئات. الفصول عبارة عن رسومات (قوالب) مكتوبة مسبقًا والتي سيتم من خلالها إنشاء الكائنات في البرنامج. علاوة على ذلك، فإن الفصل لديه دائمًا نوع محدد، والذي، بأسلوب برمجة جيد، "يخبر" غرضه من خلال اسمه. علاوة على ذلك، يمكن ملاحظة أنه نظرًا لأن Java هي لغة مكتوبة بقوة، فإن كود البرنامج يحتاج دائمًا إلى الإشارة إلى نوع الكائن عند الإعلان عن المتغيرات. أضف إلى ذلك أن الكتابة الصارمة تزيد من سلامة التعليمات البرمجية وموثوقية البرنامج وتسمح لك بمنع أخطاء عدم توافق النوع (على سبيل المثال، محاولة تقسيم سلسلة على رقم) في مرحلة الترجمة. بطبيعة الحال، يجب على المترجم "معرفة" النوع المعلن - يمكن أن يكون هذا فئة من JDK أو فئة أنشأناها بأنفسنا. يرجى ملاحظة للمحاور أنه عند العمل مع رمز البرنامج، يمكننا استخدام ليس فقط كائنات من النوع الذي قمنا بتعيينه عند الإعلان، ولكن أيضًا أحفاده. هذه نقطة مهمة: يمكننا التعامل مع العديد من الأنواع كما لو كانت نوعًا واحدًا فقط (طالما أن هذه الأنواع مشتقة من نوع أساسي). وهذا يعني أيضًا أنه بعد الإعلان عن متغير من نوع الطبقة الفائقة، يمكننا تعيين قيمة لأحد أحفاده. سوف يعجب القائم بالمقابلة إذا أعطيت مثالاً. حدد بعض الكائنات التي يمكن أن تكون مشتركة (أساسية) لمجموعة من الكائنات وارث منها فئتين. الطبقة الأساسية:

public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "I dance like everyone else.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. " ;
    }
}
في الأحفاد، تجاوز طريقة الفئة الأساسية:

public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance(){
        System.out.println(toString() + "I'm breakdancing!");
    }
}
مثال على تعدد الأشكال في Java واستخدام الكائنات في البرنامج:

public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// восходящее преобразование к базовому типу 
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
mainأظهر في كود الطريقة ما هو موجود في السطور:

Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
لقد أعلنا عن متغير نوع الطبقة الفائقة وخصصنا له قيمة أحد السلالات. على الأرجح، سيتم سؤالك عن سبب عدم شكوى المترجم من عدم التطابق بين الأنواع المعلنة على يسار ويمين علامة التعيين، حيث أن Java لديها كتابة صارمة. اشرح أن تحويل النوع التصاعدي يعمل هنا - يتم تفسير الإشارة إلى كائن على أنها إشارة إلى الفئة الأساسية. علاوة على ذلك، فإن المترجم، بعد أن واجه مثل هذا البناء في الكود، يقوم بذلك تلقائيًا وبشكل ضمني. استنادًا إلى رمز المثال، يمكن توضيح أن نوع الفئة المُعلن عنه على يسار علامة التعيين Dancerله عدة أشكال (أنواع) مُعلنة على اليمين BreakDankDancer، ElectricBoogieDancer. يمكن أن يكون لكل نموذج سلوكه الفريد الخاص به للوظيفة العامة المحددة في طريقة الطبقة الفائقة dance. وهذا يعني أن الطريقة المعلنة في الطبقة المتفوقة يمكن تنفيذها بشكل مختلف في أحفادها. في هذه الحالة، نحن نتعامل مع تجاوز الطريقة، وهذا بالضبط ما يخلق مجموعة متنوعة من الأشكال (السلوكيات). يمكنك رؤية ذلك عن طريق تشغيل كود الطريقة الرئيسية للتنفيذ: مخرجات البرنامج أنا أنطون، عمري 18 عامًا. أنا أرقص مثل أي شخص آخر. أنا أليكسي، عمري 19 سنة. أنا أرقص! أنا إيغور، عمري 20 سنة. أنا أرقص الرقصة الكهربائية! إذا لم نستخدم التجاوز في المتحدرين، فلن نحصل على سلوك مختلف. BreakDankDancerعلى سبيل المثال، إذا قمنا ElectricBoogieDancerبالتعليق على طريقة فصولنا dance، فستكون نتيجة البرنامج كما يلي: أنا أنطون، عمري 18 عامًا. أنا أرقص مثل أي شخص آخر. أنا أليكسي، عمري 19 سنة. أنا أرقص مثل أي شخص آخر. أنا إيغور، عمري 20 سنة. أنا أرقص مثل أي شخص آخر. وهذا يعني أنه ببساطة لا فائدة من إنشاء BreakDankDancerفئات جديدة ElectricBoogieDancer. ما هو بالضبط مبدأ تعدد أشكال جافا؟ أين يتم إخفاء استخدام كائن في برنامج دون معرفة نوعه المحدد؟ في مثالنا، هذا عبارة عن استدعاء d.dance()أسلوب لكائن dمن النوع Dancer. تعدد أشكال Java يعني أن البرنامج لا يحتاج إلى معرفة نوع الكائن BreakDankDancerأو الكائن ElectricBoogieDancer. الشيء الرئيسي هو أنه سليل الطبقة Dancer. وإذا كنا نتحدث عن أحفاد، تجدر الإشارة إلى أن الميراث في جاوة ليس فقط extends، ولكن أيضا implements. الآن هو الوقت المناسب لتذكر أن Java لا تدعم الوراثة المتعددة - يمكن أن يكون لكل نوع أصل واحد (فئة فائقة) وعدد غير محدود من المتحدرين (فئات فرعية). لذلك، يتم استخدام الواجهات لإضافة وظائف متعددة إلى الفئات. تعمل الواجهات على تقليل اقتران الكائنات بأحد الوالدين مقارنة بالميراث وتستخدم على نطاق واسع جدًا. في Java، تعد الواجهة نوعًا مرجعيًا، لذلك يمكن للبرنامج أن يعلن عن النوع كمتغير لنوع الواجهة. هذا هو الوقت المناسب لإعطاء مثال. لنقم بإنشاء الواجهة:

public interface Swim {
    void swim();
}
من أجل الوضوح، لنأخذ كائنات مختلفة وغير ذات صلة وننفذ واجهة فيها:

public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"I swim with an inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}
 
public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish " + name + ". I swim by moving my fins.");

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
طريقة main:

public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("whale");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
تتيح لنا نتيجة تنفيذ طريقة متعددة الأشكال محددة في الواجهة رؤية الاختلافات في سلوك الأنواع التي تنفذ تلك الواجهة. وهي تتكون من نتائج مختلفة لتنفيذ الطريقة swim. بعد دراسة مثالنا، قد يسأل القائم بالمقابلة عن السبب عند تنفيذ الكود منmain

for (Swim s : swimmers) {
            s.swim();        
}
هل الأساليب المحددة في هذه الفئات تسمى لكائناتنا؟ كيف يمكنك تحديد التنفيذ المطلوب للطريقة عند تنفيذ البرنامج؟ للإجابة على هذه الأسئلة، علينا أن نتحدث عن الربط المتأخر (الديناميكي). نعني بالربط إنشاء اتصال بين استدعاء الأسلوب وتنفيذه المحدد في الفئات. بشكل أساسي، يحدد الكود أي من الطرق الثلاث المحددة في الفئات سيتم تنفيذها. تستخدم Java بشكل افتراضي الربط المتأخر (في وقت التشغيل بدلاً من وقت الترجمة، كما هو الحال مع الربط المبكر). وهذا يعني أنه عند تجميع التعليمات البرمجية

for (Swim s : swimmers) {
            s.swim();        
}
لا يعرف المترجم بعد الفئة التي ينتمي إليها الكود Human، Fishأو ما إذا Uboatكان سيتم تنفيذه في ملف swim. سيتم تحديد ذلك فقط عند تنفيذ البرنامج بفضل آلية الإرسال الديناميكي - التحقق من نوع الكائن أثناء تنفيذ البرنامج واختيار التنفيذ المطلوب للطريقة لهذا النوع. إذا سُئلت عن كيفية تنفيذ ذلك، فيمكنك الإجابة على أنه عند تحميل الكائنات وتهيئتها، يقوم JVM ببناء جداول في الذاكرة، ويقوم فيها بربط المتغيرات بقيمها، والكائنات بأساليبها. علاوة على ذلك، إذا تم توريث كائن ما أو تنفيذ واجهة، فسيتم التحقق أولاً من وجود الأساليب التي تم تجاوزها في فئته. إذا كان هناك أي منها، فهي مرتبطة بهذا النوع، وإذا لم يكن الأمر كذلك، فسيتم البحث عن طريقة تم تحديدها في الفئة ذات المستوى الأعلى (في الأصل) وما إلى ذلك حتى الجذر في تسلسل هرمي متعدد المستويات. عند الحديث عن تعدد الأشكال في OOP وتنفيذه في كود البرنامج، نلاحظ أنه من الممارسات الجيدة استخدام الأوصاف المجردة لتحديد الفئات الأساسية باستخدام الفئات المجردة وكذلك الواجهات. تعتمد هذه الممارسة على استخدام التجريد - عزل السلوك والخصائص المشتركة وإحاطتها بفئة مجردة، أو عزل السلوك المشترك فقط - وفي هذه الحالة نقوم بإنشاء واجهة. يعد بناء وتصميم تسلسل هرمي للكائنات بناءً على الواجهات والميراث الطبقي شرطًا أساسيًا لتحقيق مبدأ تعدد الأشكال OOP. فيما يتعلق بمسألة تعدد الأشكال والابتكارات في Java، يمكننا أن نذكر أنه عند إنشاء فئات وواجهات مجردة، بدءًا من Java 8، من الممكن كتابة التنفيذ الافتراضي للطرق المجردة في الفئات الأساسية باستخدام الكلمة الأساسية default. على سبيل المثال:

public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
في بعض الأحيان قد يسألون عن متطلبات الإعلان عن الأساليب في الفئات الأساسية حتى لا يتم انتهاك مبدأ تعدد الأشكال. كل شيء بسيط هنا: لا ينبغي أن تكون هذه الأساليب ثابتة وخاصة ونهائية . الخاص يجعل الطريقة متاحة فقط في الفصل، ولا يمكنك تجاوزها في التابع. Static يجعل الطريقة خاصية للفئة، وليس الكائن، لذلك سيتم دائمًا استدعاء طريقة الطبقة الفائقة. النهائي سيجعل الطريقة ثابتة ومخفية عن ورثتها.

ماذا يعطينا تعدد الأشكال في جافا؟

من المرجح أيضًا أن يطرح السؤال حول ما الذي يعطينا إياه استخدام تعدد الأشكال. هنا يمكنك الإجابة بإيجاز، دون التعمق في الأعشاب الضارة:
  1. يسمح لك باستبدال تطبيقات الكائنات. وهذا ما يعتمد عليه الاختبار.
  2. يوفر إمكانية توسيع البرنامج - يصبح إنشاء أساس للمستقبل أسهل بكثير. تعد إضافة أنواع جديدة بناءً على الأنواع الموجودة هي الطريقة الأكثر شيوعًا لتوسيع وظائف البرامج المكتوبة بأسلوب OOP.
  3. يسمح لك بدمج الكائنات ذات النوع أو السلوك الشائع في مجموعة أو مصفوفة واحدة وإدارتها بشكل موحد (كما في أمثلةنا، جعل الجميع يرقصون - طريقة danceأو يسبحون - طريقة swim).
  4. المرونة عند إنشاء أنواع جديدة: يمكنك اختيار تنفيذ طريقة من أحد الوالدين أو تجاوزها في طفل.

كلمات فراق للرحلة

يعد مبدأ تعدد الأشكال موضوعًا مهمًا وواسعًا للغاية. وهو يغطي ما يقرب من نصف OOP في Java وجزءًا كبيرًا من أساسيات اللغة. لن تتمكن من الإفلات من تحديد هذا المبدأ في المقابلة. من المرجح أن يؤدي الجهل أو سوء الفهم إلى إنهاء المقابلة. لذلك لا تتكاسل في التحقق من معلوماتك قبل الاختبار وتحديثها إذا لزم الأمر.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION