تعدد الأشكال والتجريد والواجهات
الكائن الواحد يأخذ أشكالًا عدة، والاستدعاء الواحد يتصرّف بحسب الكائن الفعلي.
- تشرح مفهوم تعدد الأشكال وأنواعه.
- تعيد كتابة دالة موروثة Overriding.
- تنشئ كلاسًا مجردًا abstract بدوال مجردة.
- تكتب واجهة interface وتنفذها في أكثر من كلاس.
١الفكرة
Polymorphism كلمة يونانية: Poly = كثير، وMorph = شكل. في البرمجة: أداء مهمة واحدة بطرق مختلفة، وقدرة الكائن على أن يُعامل كنوع أبيه.
٢نوعاه في جافا
الثابت: التحميل الزائد
package polymorephism;public class Main { void sum(int x, int y) { int s = x + y; System.out.println("Sum of two numbers: " + s); } void sum(int x, int y, int z) { int s = x + y + z; System.out.println("Sum of three numbers: " + s); } public static void main(String[] args) { Main m = new Main(); m.sum(20, 10); m.sum(10, 20, 30); }}
أثناء الترجمة (قبل التنفيذ) يطابق المترجِم القيم المرسلة ويربط كل استدعاء بالدالة المناسبة؛ لذلك يُسمّى الربط المبكر Early Binding.
٣الديناميكي: إعادة كتابة الدوال Overriding
- نكتب
@Overrideقبل الدالة في الابن، فيتحقق المترجِم أنها فعلًا موجودة في الأب. - في NetBeans: Insert Code ← Override Method ← اختر الدالة (انظر الخطوات بالصور أدناه).
- لا يمكن إعادة كتابة دالة private أو static أو final.
- عند الاستدعاء يحدّد JVM أثناء التشغيل أي نسخة تُنفَّذ حسب الكائن الفعلي؛ لذلك يُسمّى الربط المتأخر.
إعادة كتابة دالة بمساعدة NetBeans
- داخل الكلاس الابن (مثل HourlyEmployee) انقر بالزر الأيمن، واختر Insert Code (أو Alt+Insert)، ثم من قائمة Generate اختر Override Method.

- حدّد الدالة التي تريد إعادة كتابتها، مثل getSalary() من الكلاس Employee، ثم اضغط Generate. يضيف NetBeans الدالة ومعها @Override، فتكتب أنت جسمها الجديد.

| Overloading | Overriding | |
|---|---|---|
| أين؟ | في الكلاس نفسه | بين الأب والابن |
| المعاملات | مختلفة | مطابقة تمامًا |
| متى يُحدَّد؟ | وقت الترجمة | وقت التشغيل |
المثال التطبيقي هو التدريب 13 أدناه: متغير واحد emp يأخذ ثلاثة أشكال، فيطبع ثلاثة رواتب مختلفة.
٤ما هو التجريد؟
هذه هي المبادئ الأربعة للبرمجة الشيئية، ونكون بهذا الدرس قد درسناها كلها.
٥الكلاس المجرد abstract class
| القاعدة | التوضيح |
|---|---|
يُعرَّف بـ abstract class | لا يمكن إنشاء كائن منه: new Shape() خطأ ترجمة. |
| قد يحتوي دوال مجردة | دالة بلا جسم تنتهي بـ ; مثل abstract double getArea(); |
| وقد يحتوي دوالًا عادية وخصائص | تُورث جاهزة للأبناء. |
| الابن غير المجرد | يجب أن يكتب كل الدوال المجردة، وإلا فهو خطأ ترجمة. |
٦متى نستخدم الكلاس المجرد؟
- تشابه في الفكرة واختلاف في التفاصيل: كل الأشكال لها مساحة، لكن Circle و Rectangle يحسبانها بطريقتين.
- منع إنشاء كائن من الأب: لا نستطيع حساب مساحة «شكل عام» قبل تحديد نوعه:
Shape s = new Shape();خطأ. - إجبار الأبناء على كتابة دوال معيّنة: أي كلاس يرث Shape يجب أن يكتب getArea، وإلا ظهر خطأ. وهذا ممتاز لأنه يُلزم المبرمجين بالتصميم العام.
| العنصر | الوصف |
|---|---|
| abstract class | كلاس لا يمكن إنشاء كائن منه مباشرة |
| abstract method | دالة بلا جسم، تُعرَّف فقط وتُنفَّذ في الكلاس الابن |
| هل توجد دالة عادية داخل الكلاس المجرد؟ | نعم |
| هل يجب على الابن تنفيذ الدوال المجردة؟ | نعم |
ويتحقق التجريد أيضًا بالواجهات interfaces، وتتعرّف عليها لاحقًا.
٧ما هي الواجهة Interface؟
تذكّر الكلاس المجرد abstract في القسم السابق: الواجهة تشبهه، لكنها في صورتها الأساسية مجردة بالكامل؛ لا تحتوي دوالًا عادية ولا خصائص متغيرة.
٨كتابة الواجهة وتنفيذها
نكتب الواجهة بالكلمة interface بدل class:
public interface Printable { void printInfo(); // دالة بلا جسم}
ثم ينفّذها الكلاس بالكلمة implements، ويكتب جسم الدالة:
public class Student implements Printable { int id; String name; double mark; public Student(int id, String name, double mark) { this.id = id; this.name = name; this.mark = mark; } @Override public void printInfo() { System.out.println("Student: " + id + " - " + name + " - " + mark); }}
- دوال الواجهة عامة public ومجردة تلقائيًّا، فلا نكتب لها جسمًا.
- الكلاس الذي ينفّذ الواجهة يجب أن يكتب جميع دوالها، وإلا ظهر خطأ ترجمة.
- لا يمكن إنشاء كائن من الواجهة:
new Printable()خطأ. - لكن يمكن أن يكون المتغير أو المصفوفة من نوع الواجهة، كما في المثال التالي.
٩مثال: كلاسان مختلفان ينفّذان الواجهة نفسها
الطالب والكتاب لا علاقة وراثة بينهما، لكن كليهما يستطيع أن «يطبع بياناته». الواجهة تجمعهما، فنضعهما في مصفوفة واحدة من نوع Printable:
public interface Printable { void printInfo(); // دالة بلا جسم}
ملفات المشروع في تبويبات كما في NetBeans. زر التشغيل يجمعها في ملف واحد ليعمل على OneCompiler.
p من نوع Printable، لكن في كل دورة يحمل كائنًا مختلفًا، فتُنفَّذ نسخة الكائن الفعلي: هذا تعدد الأشكال الذي درسته.
١٠تنفيذ أكثر من واجهة
الكلاس في جافا يرث كلاسًا واحدًا فقط بـ extends، لكنه يستطيع تنفيذ أكثر من واجهة بفاصلة بينها. البطة تسبح وتطير:
interface Swimmer { void swim();} interface Flyer { void fly();} class Duck implements Swimmer, Flyer { public void swim() { System.out.println("البطة تسبح"); } public void fly() { System.out.println("البطة تطير"); }} public class Main { public static void main(String[] args) { Duck d = new Duck(); d.swim(); d.fly(); }}
١١الكلاس المجرد أم الواجهة؟
| abstract class | interface | |
|---|---|---|
| الكلمة | extends | implements |
| عدد ما يرثه/ينفّذه الكلاس | كلاس واحد فقط | أكثر من واجهة |
| الدوال | مجردة وعادية | مجردة (في صورتها الأساسية) |
| الخصائص | أي خصائص | ثوابت فقط |
| إنشاء كائن منه | لا | لا |
| متى نستخدمه؟ | كلاسات متقاربة بينها كود مشترك | قدرة مشتركة بين كلاسات مختلفة |
توقّع وتحقّق
تأمل الكود التالي،
class Employee { private double salary = 1500; public double getSalary() { return salary; }}class HourlyEmployee extends Employee { private int workingHours = 40; private double hourRate = 150; @Override public double getSalary() { return workingHours * hourRate; }} // main داخل الدالةEmployee e = new HourlyEmployee();System.out.println(e.getSalary());
ماذا سيكون الناتج عند تنفيذ آخر سطرين؟
تأمل الكود التالي،
class Shape {}class Circle extends Shape { Circle(double radius) { }}class Rectangle extends Shape { Rectangle(double w, double h) { }}
الكلاسان Circle و Rectangle يرثان الكلاس Shape. أي سطر من الأسطر التالية صحيح؟
من يحدّد الدالة المنفَّذة في تعدد الأشكال الديناميكي؟
أي سطر خطأ إذا كان Shape كلاسًا مجردًا؟
الدالة المجردة:
الكلاس الابن يرث الكلاس المجرد بالكلمة:
كلاس ابن لم يكتب دالة مجردة ورثها، وهو غير مجرد:
الكلمة التي تجعل الكلاس ينفّذ واجهة:
ما الذي يجب على الكلاس الذي ينفّذ الواجهة Printable؟
أي سطر صحيح؟
إذا كان الكلاس Book ينفّذ الواجهة Printable:
كم واجهة يستطيع الكلاس الواحد أن ينفّذها؟
الخلاصة
- تعدد الأشكال: الاستدعاء نفسه يتصرف بحسب نوع الكائن.
- Overriding: الابن يعيد كتابة دالة الأب بالتوقيع نفسه.
- الكلاس المجرد لا يُنشأ منه كائن، ويُلزم أبناءه بتنفيذ دواله المجردة.
- الواجهة تحدد ما يجب على الكلاس فعله، ويمكن للكلاس تنفيذ أكثر من واجهة.