متحدون جافا رقم 3: تعدد الأشكال والوراثة
نواصل ترجمة سلسلة من المقالات مع مشاكل جافا. تسببت آخر مشاركة حول السطور في مناقشة ساخنة بشكل مدهش. نأمل أن لا تمر بهذه المقالة أيضًا. ونعم - نحن الآن ندعوك لحضور الذكرى السنوية العاشرة لدورة مطور جافا .
وفقا ل Venkat Subramaniam الأسطوري ، تعدّد الأشكال هو المفهوم الأكثر أهمية في البرمجة الشيئية. إن تعدد الأشكال - أو قدرة الكائن على تنفيذ إجراءات متخصصة بناءً على نوعه - هو ما يجعل كود Java مرنًا. تستخدم أنماط التصميم مثل Command ، و Observer ، و Decorator ، و Strategy ، والعديد من الأنماط الأخرى التي تم إنشاؤها بواسطة Gang of Four شكلاً من أشكال تعدد الأشكال. سيحسن إتقان هذا المفهوم من قدرتك على التفكير من خلال حلول البرامج.

يمكنك أخذ شفرة المصدر لهذه المقالة والتجربة هنا: https://github.com/rafadelnero/javaworld-challengers
واجهات والميراث في تعدد الأشكال
في هذه المقالة ، سنركز على العلاقة بين تعدد الأشكال والميراث. الشيء الرئيسي الذي يجب تذكره هو أن تعدد الأشكال يتطلب الوراثة أو تنفيذ واجهة . يمكنك مشاهدة هذا في المثال أدناه مع Duke و Juggy
:
public abstract class JavaMascot { public abstract void executeAction(); } public class Duke extends JavaMascot { @Override public void executeAction() { System.out.println("Punch!"); } } public class Juggy extends JavaMascot { @Override public void executeAction() { System.out.println("Fly!"); } } public class JavaMascotTest { public static void main(String... args) { JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); } }
سيكون إخراج هذا الرمز على النحو التالي:
Punch! Fly!
نظرًا Juggy
يتم تحديد تطبيقات محددة ، سيتم استدعاء كل من طرق Duke
و Juggy
.
هل الطريقة تفرط في تعدد الأشكال؟ يخلط العديد من المبرمجين علاقة تعدد الأشكال بالسيطرة والحمل الزائد . في الواقع ، إعادة تعريف الطريقة فقط هي تعدد الأشكال الحقيقي. يستخدم التحميل الزائد نفس اسم الطريقة ، ولكن معلمات مختلفة. تعددية الأشكال مصطلح واسع النطاق ، لذلك ستكون هناك دائمًا مناقشات حول هذا الموضوع.
ما هو الغرض من تعدد الأشكال
ميزة كبيرة والغرض من استخدام تعدد الأشكال هو الحد من اتصال فئة العميل مع التنفيذ. بدلاً من الرمز الصلب ، تحصل فئة العميل على تنفيذ التبعية لتنفيذ الإجراء اللازم. وبالتالي ، فإن فئة العميل تعرف الحد الأدنى لإكمال إجراءاتها ، وهو مثال على الربط الضعيف.
لفهم الغرض من تعدد الأشكال بشكل أفضل ، ألق نظرة على SweetCreator
:
public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List<SweetProducer> sweetProducer; public SweetCreator(List<SweetProducer> sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList( new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } }
في هذا المثال ، يمكنك أن ترى أن فئة SweetCreator
تعرف SweetCreator
عن فئة SweetProducer
. لا يعرف تنفيذ كل Sweet
. يمنحنا هذا الفصل المرونة لتحديث وإعادة استخدام الفصول الدراسية لدينا ، وهذا يجعل الحفاظ على التعليمات البرمجية أسهل بكثير. عند تصميم التعليمات البرمجية ، ابحث دائمًا عن طرق لجعلها مرنة ومريحة بقدر الإمكان. تعددية الأشكال هي طريقة قوية جدًا لاستخدامها لهذا الغرض.
يتطلب @Override
من المبرمج استخدام نفس توقيع الطريقة ، والذي يجب تجاوزه. إذا لم يتم تجاوز الطريقة ، فسيكون هناك خطأ في الترجمة.
أنواع الإرجاع المتغايرة عند تجاوز طريقة
يمكنك تغيير نوع الإرجاع لطريقة تم تجاوزها إذا كانت من النوع المتغاير . النوع المتغير هو في الأساس فئة فرعية للقيمة المرتجعة.
فكر في مثال:
public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } }
نظرًا لأن Duke
هو JavaMascot
، يمكننا تغيير نوع القيمة المرجعة عند التجاوز.
تعدد الأشكال في فئات جافا الأساسية
نحن نستخدم باستمرار تعدد الأشكال في فئات جافا الأساسية. أحد الأمثلة البسيطة للغاية هو إنشاء فئة ArrayList
بإعلان نوع كواجهة List
.
List<String> list = new ArrayList<>();
ضع في اعتبارك نموذجًا لرمز يستخدم واجهة برمجة تطبيقات مجموعات جافا بدون تعدد الأشكال:
public class ListActionWithoutPolymorphism {
رمز مثير للاشمئزاز ، أليس كذلك؟ تخيل أنك بحاجة لمرافقته! الآن فكر في نفس المثال مع تعدد الأشكال:
public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) {
ميزة تعدد الأشكال هي المرونة والقابلية للتوسعة. بدلاً من إنشاء العديد من الطرق المختلفة ، يمكننا أن نعلن طريقة واحدة تحصل على نوع List
.
استدعاء طرق محددة لطريقة متعددة الأشكال
يمكنك استدعاء طرق محددة مع استدعاء أسلوب متعدد الأشكال ، وذلك بسبب المرونة. هنا مثال:
public abstract class MetalGearCharacter { abstract void useWeapon(String weapon); } public class BigBoss extends MetalGearCharacter { @Override void useWeapon(String weapon) { System.out.println("Big Boss is using a " + weapon); } void giveOrderToTheArmy(String orderMessage) { System.out.println(orderMessage); } } public class SolidSnake extends MetalGearCharacter { void useWeapon(String weapon) { System.out.println("Solid Snake is using a " + weapon); } } public class UseSpecificMethod { public static void executeActionWith(MetalGearCharacter metalGearCharacter) { metalGearCharacter.useWeapon("SOCOM");
التقنية التي نستخدمها هنا هي صب نوع كائن أو تغييره بوعي في وقت التشغيل.
يرجى ملاحظة أنه لا يمكن استدعاء طريقة محددة إلا عند إرسال نوع أكثر عمومية إلى نوع أكثر تحديدًا. التشابه الجيد هو إخبار المترجم صراحةً: "مهلاً ، أنا أعرف ما أفعله هنا ، لذلك سأقوم بإلقاء الكائن على نوع معين وسأستخدم هذه الطريقة."
بالإشارة إلى المثال أعلاه ، فإن المترجم لديه سبب وجيه لعدم قبول استدعاء طرق معينة: يجب أن تكون الفئة التي تم تمريرها SolidSnake
. في هذه الحالة ، لا يوجد لدى المحول البرمجي أي طريقة للتأكد من أن كل فئة فرعية من MetalGearCharacter
لديها طريقة giveOrderToTheArmy
.
Instanceof الكلمة الأساسية
لاحظ instanceof
الكلمة المحجوزة. قبل استدعاء طريقة معينة ، سألنا إذا كان MetalGearCharacter
مثال لـ BigBoss
. إذا لم يكن هذا مثيل لـ BigBoss
، فسوف نحصل على الاستثناء التالي:
Exception in thread `main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
الكلمات الرئيسية super
ماذا لو أردنا الرجوع إلى سمة أو أسلوب من الفصل الأصل؟ في هذه الحالة ، يمكننا استخدام الكلمة الأساسية super
.
على سبيل المثال:
public class JavaMascot { void executeAction() { System.out.println("The Java Mascot is about to execute an action!"); } } public class Duke extends JavaMascot { @Override void executeAction() { super.executeAction(); System.out.println("Duke is going to punch!"); } public static void main(String... superReservedWord) { new Duke().executeAction(); } }
executeAction
استخدام الكلمة المحجوزة super
في طريقة executeAction
لفئة Duke
طريقة الفصل الأصل. ثم نقوم بعمل معين من فئة Duke
. لهذا السبب يمكننا رؤية كلتا الرسالتين في الإخراج:
The Java Mascot is about to execute an action! Duke is going to punch!
حل مشكلة تعدد الأشكال
دعونا نتحقق مما تعلمته عن تعدد الأشكال والميراث.
في هذه المهمة ، يتم إعطاؤك عدة طرق من Matt Groening's The Simpsons ، من Wavs يُطلب منك كشف ما سيكون الناتج لكل فئة. أولاً ، قم بتحليل التعليمات البرمجية التالية بعناية:
public class PolymorphismChallenge { static abstract class Simpson { void talk() { System.out.println("Simpson!"); } protected void prank(String prank) { System.out.println(prank); } } static class Bart extends Simpson { String prank; Bart(String prank) { this.prank = prank; } protected void talk() { System.out.println("Eat my shorts!"); } protected void prank() { super.prank(prank); System.out.println("Knock Homer down"); } } static class Lisa extends Simpson { void talk(String toMe) { System.out.println("I love Sax!"); } } public static void main(String... doYourBest) { new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); } }
ما رأيك؟ ماذا ستكون النتيجة؟ لا تستخدم IDE لمعرفة! الهدف هو تحسين مهارات تحليل التعليمات البرمجية الخاصة بك ، لذا حاول أن تقرر بنفسك.
اختر إجابتك (يمكنك العثور على الإجابة الصحيحة في نهاية المقالة).
أ)
أحب ساكس!
D'oh
سيمبسون!
D'oh
ب)
ساكس :)
أكل سروالي!
أحب ساكس!
D'oh
اهدم هوميروس
ج)
ساكس :)
D'oh
سيمبسون!
اهدم هوميروس
د)
أحب ساكس!
أكل سروالي!
سيمبسون!
D'oh
اهدم هوميروس
ماذا حدث فهم تعدد الأشكال
للحصول على الطريقة التالية:
new Lisa().talk("Sax :)");
الإخراج سيكون "أنا أحب ساكس!". هذا لأننا نمرر السلسلة إلى الطريقة ولدى فئة Lisa
مثل هذه الطريقة.
للاتصال التالي:
Simpson simpson = new Bart("D'oh"); simpson.talk();
الإخراج سيكون "أكل شورتاتي!". هذا لأننا Simpson
نوع Simpson
مع Bart
.
الآن انظروا ، هذا أكثر صعوبة:
Lisa lisa = new Lisa(); lisa.talk();
هنا نستخدم طريقة التحميل الزائد مع الميراث. لا ننقل أي شيء إلى طريقة talk
، لذلك Simpson
استدعاء طريقة talk
من Simpson
.
في هذه الحالة ، سيكون الإخراج "Simpson!".
هنا واحد آخر:
((Bart) simpson).prank();
في هذه الحالة ، تم تمرير سلسلة prank
عند إنشاء فئة Bart
خلال new Bart("D'oh");
. في هذه الحالة ، تُسمى طريقة super.prank()
أولاً ، ثم تُسمى طريقة prank()
من فئة Bart
. ستكون النتيجة:
"D'oh" "Knock Homer down"
أخطاء تعدد الأشكال الشائعة
الخطأ الشائع هو التفكير في أنه يمكنك استدعاء طريقة محددة دون استخدام نوع المدلى بها.
خطأ آخر هو عدم اليقين بشأن الطريقة التي سيتم استدعاؤها عند نسخ الطبقة بشكل متعدد الأشكال. تذكر أن الطريقة التي يتم استدعاؤها هي طريقة المثيل الفوري.
تذكر أيضًا أن تجاوز الطريقة ليس عبءًا زائدًا على الطريقة.
لا يمكن تجاوز الطريقة إذا كانت المعلمات مختلفة. يمكنك تغيير نوع الإرجاع لأسلوب تم تجاوزه إذا كان نوع الإرجاع فئة فرعية.
ما تحتاج إلى تذكره حول تعدد الأشكال
يحدد المثال الذي تم إنشاؤه الطريقة التي سيتم استدعاؤها عند استخدام تعدد الأشكال.
يتطلب @Override
من المبرمج استخدام طريقة تم تجاوزها ؛ خلاف ذلك ، سيحدث خطأ مترجم.
يمكن استخدام تعدد الأشكال مع الفصول العادية والفصول المجردة والواجهات.
تعتمد معظم أنماط التصميم على شكل من أشكال تعدد الأشكال.
الطريقة الوحيدة لاستدعاء طريقتك في فئة فرعية متعددة الأشكال هي استخدام نوع الصب.
يمكنك إنشاء بنية تعليمات برمجية قوية باستخدام تعدد الأشكال.
التجربة. من خلال هذا ، ستتمكن من إتقان هذا المفهوم القوي!
الجواب
الجواب د.
ستكون النتيجة:
I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down
كالعادة ، أرحب بتعليقاتكم وأسئلتكم. ونحن ننتظر فيتالي في درس مفتوح .