الوراثة والواجهات في Java: abstract و interface
في درس الكلاسات رأيت كيف ترث القطة من الحيوان بكلمة extends. لكن الوراثة في المشاريع الحقيقية أعمق من ذلك: أحياناً نريد كلاساً أباً لا يُنشأ منه كائن أصلاً، وأحياناً نريد أن تتفق كلاسات مختلفة تماماً على قدرة مشتركة. في هذا الدرس ستتعلم الكلاسات المجردة والواجهات، وهما أساس تصميم أي برنامج Java كبير.
مراجعة سريعة: extends و super
Section titled “مراجعة سريعة: extends و super”class Employee { protected String name; protected double salary;
public Employee(String name, double salary) { this.name = name; this.salary = salary; }
public double bonus() { return salary * 0.10; }
public String describe() { return name + " - المكافأة: " + bonus(); }}
class Manager extends Employee { public Manager(String name, double salary) { super(name, salary); }
@Override public double bonus() { return super.bonus() + 500; }}Employee e = new Employee("هدى", 4000);Employee m = new Manager("كريم", 6000);System.out.println(e.describe());System.out.println(m.describe());هدى - المكافأة: 400.0كريم - المكافأة: 1100.0الجديد هنا هو super.bonus(): استدعاء نسخة الأب من الدالة ثم البناء عليها، بدل إعادة كتابة الحساب كله. ولاحظ أن describe مكتوبة مرة واحدة في الأب، ومع ذلك استخدمت مكافأة المدير الصحيحة. هذا هو تعدد الأشكال (Polymorphism): الكائن نفسه يحدد أي نسخة من الدالة تعمل.
الكلاس المجرد abstract
Section titled “الكلاس المجرد abstract”ما مساحة «شكل»؟ السؤال بلا معنى حتى نعرف هل هو دائرة أم مستطيل. الكلاس Shape فكرة عامة فقط، ولا يصح إنشاء كائن منه. نعبّر عن ذلك بكلمة abstract:
abstract class Shape { protected String name;
public Shape(String name) { this.name = name; }
public abstract double area();
public void print() { System.out.println(name + ": " + area()); }}
class Rectangle extends Shape { private double width, height;
public Rectangle(double width, double height) { super("مستطيل"); this.width = width; this.height = height; }
@Override public double area() { return width * height; }}
class Square extends Rectangle { public Square(double side) { super(side, side); this.name = "مربع"; }}Shape[] shapes = { new Rectangle(4, 5), new Square(3) };for (Shape s : shapes) { s.print();}مستطيل: 20.0مربع: 9.0abstract class: كلاس لا يمكن إنشاء كائن منه. السطرnew Shape("شكل")يرفضه المترجم.abstract double area();: دالة بلا جسم. كل كلاس وارث ملزم بكتابتها، وإلا لن يُترجم.- الكلاس المجرد يستطيع مع ذلك أن يحمل حقولاً ودوالاً عادية مثل
print.
الكلاس المجرد مثل نموذج طلب فيه خانات فارغة: الهيكل جاهز، لكن لا يُقبل النموذج حتى يملأ صاحبه الخانات الإلزامية.
الواجهة interface
Section titled “الواجهة interface”الطائر يطير، والطائرة تطير، لكنهما لا ينتميان لعائلة واحدة. ما يجمعهما قدرة وليست قرابة. الواجهة (Interface) تصف قدرة: قائمة دوال يتعهد الكلاس بتنفيذها:
interface Printable { void print();}
interface Payable { double TAX = 0.14;
double total();
default String receipt() { return "الإجمالي: " + total(); }}
class Invoice implements Printable, Payable { private double amount;
public Invoice(double amount) { this.amount = amount; }
@Override public void print() { System.out.println("فاتورة بمبلغ " + amount); }
@Override public double total() { return amount + amount * TAX; }}Invoice inv = new Invoice(200);inv.print();System.out.println(inv.receipt());
Payable p = inv;System.out.println(p.total());فاتورة بمبلغ 200.0الإجمالي: 228.0228.0implements: الكلاس يتعهد بتنفيذ كل دوال الواجهة.- الكلاس يرث من أب واحد فقط بـ
extends، لكنه ينفّذ أي عدد من الواجهات. - دوال الواجهة
publicوabstractتلقائياً، وأي متغير فيها ثابت (public static final). default: دالة لها جسم جاهز داخل الواجهة، يرثها الكلاس كما هي أو يعيد كتابتها.
الواجهة مثل رخصة القيادة: لا يهم من أنت ولا من أي عائلة، المهم أنك أثبتّ قدرتك على القيادة.
instanceof: ما النوع الحقيقي؟
Section titled “instanceof: ما النوع الحقيقي؟”متغير من نوع الأب قد يحمل كائناً من أي ابن. للتحقق من النوع الحقيقي والوصول لدواله الخاصة نستخدم instanceof:
Object[] items = { "نص", 42, 3.5 };for (Object item : items) { if (item instanceof String s) { System.out.println("نص طوله " + s.length()); } else if (item instanceof Integer n) { System.out.println("عدد صحيح ضعفه " + (n * 2)); } else { System.out.println("نوع آخر: " + item); }}نص طوله 2عدد صحيح ضعفه 84نوع آخر: 3.5الصيغة item instanceof String s تفحص النوع وتعرّف المتغير s جاهزاً في خطوة واحدة. ولاحظ أن كل كلاس في Java يرث تلقائياً من الكلاس Object، ولذلك تقبل المصفوفة أي شيء.
كلاس مجرد أم واجهة؟
Section titled “كلاس مجرد أم واجهة؟”| الكلاس المجرد | الواجهة | |
|---|---|---|
| العلاقة | «هو نوع من» | «يستطيع أن» |
| العدد المسموح | أب واحد | أي عدد |
| الحقول | حقول عادية ومُنشئ | ثوابت فقط |
| مثال | Shape و Rectangle |
Comparable و Runnable |
القاعدة العملية: ابدأ بالواجهة. واختر الكلاس المجرد فقط إذا كانت الكلاسات الوارثة تشترك في حقول وكود فعلي.
final: ممنوع التعديل
Section titled “final: ممنوع التعديل”كلمة final أمام كلاس تمنع الوراثة منه، وأمام دالة تمنع إعادة كتابتها:
final class Token { public final String value() { return "abc123"; }}الكلاس String نفسه final، ولهذا لا يستطيع أحد أن يرث منه ويغيّر سلوكه.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
abstract class Vehicleفيه حقلnameودالة مجردةwheels()ودالة عاديةinfo()تطبع الاسم وعدد العجلات. - أنشئ
CarوBikeيرثان منه. - أنشئ
interface Electricفيها الدالةcharge()، واجعل كلاساً جديداًElectricCarيرث منCarوينفّذElectric. - ضع المركبات في مصفوفة واطبع معلوماتها، واشحن فقط ما ينفّذ
Electricباستخدامinstanceof.
أخطاء شائعة
Section titled “أخطاء شائعة”- إنشاء كائن من كلاس مجرد:
new Shape(...)يعطي الخطأShape is abstract; cannot be instantiated. - نسيان تنفيذ دالة: إذا لم ينفّذ الكلاس كل الدوال المجردة ستقرأ:
is not abstract and does not override abstract method. - إضعاف الوصول: دوال الواجهة
public، فتنفيذها يجب أن يُكتبpublicأيضاً. نسيان الكلمة خطأ ترجمة. - نسيان @Override: بدونها قد تخطئ في اسم الدالة فتنشئ دالة جديدة دون أن تدري.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
كم واجهة يستطيع الكلاس الواحد أن ينفّذ بكلمة implements؟
الكلاس يرث من أب واحد فقط، لكنه ينفّذ أي عدد من الواجهات مفصولة بفواصل.
سؤال
ماذا يحدث إذا ورث كلاس عادي من كلاس مجرد ولم يكتب دالته المجردة؟
الدوال المجردة إلزامية: إما أن ينفّذها الكلاس الوارث، أو يُعلن هو نفسه abstract.
الخلاصة
Section titled “الخلاصة”super.method()تستدعي نسخة الأب، وتعدد الأشكال يختار نسخة الكائن الحقيقي.abstract class: كلاس لا يُنشأ منه كائن، وقد يحوي دوالاً مجردة يلتزم بها الأبناء.interfaceتصف قدرة، والكلاس ينفّذها بـimplementsوبأي عدد.instanceofتفحص النوع الحقيقي للكائن وتعرّف متغيراً منه.finalتمنع الوراثة من الكلاس أو إعادة كتابة الدالة.