الوراثة وتعدّد الأشكال

الفئات المجردة (مقدمة)

15 دقيقة الدرس 8 من 14

الفئات المجردة (مقدمة)

حتى الآن كانت كل فئة كتبتها قابلة للإنشاء — بإمكانك استدعاء new Dog() أو new Circle() وما شابه ذلك. لكن أحيانًا تُوجد الفئة لتكون مخططًا صرفًا تلتزم به الفئات الأخرى. لا معنى لإنشاء كائن Shape خالٍ إن لم تنوِ رسمه مباشرةً. يمنحك Java أداةً دقيقة لهذا الغرض: الفئة المجردة (abstract class).

ماذا تعني الكلمة abstract؟

إضافة الكلمة المفتاحية abstract إلى تصريح الفئة يخبر المُترجم بأمرين:

  1. هذه الفئة لا يمكن إنشاء كائنات منها مباشرةً. استدعاء new Shape() خطأ يُكشف في وقت الترجمة.
  2. قد تحتوي على توابع مجردة — توقيعات توابع بلا جسم — يجب أن تُنفّذها الفئات الفرعية إلزاميًا.
الفكرة الأساسية: تُمثّل الفئة المجردة مفهومًا غير مكتمل. تحدّد ما يجب على الفئات الفرعية فعله (عبر التوابع المجردة)، وقد توفّر أيضًا سلوكًا مشتركًا ترثه الفئات الفرعية مجانًا (عبر التوابع الملموسة).

تصريح الفئة المجردة والتوابع المجردة

إليك الصياغة:

public abstract class Shape { // تابع مجرد: يُعرّف ماذا نفعل، لا كيف نفعله public abstract double area(); // تابع ملموس: تنفيذ مشترك ترثه جميع الفئات الفرعية public String describe() { return "I am a shape with area " + area(); } }

التابع area() لا يحتوي على جسم — فقط فاصلة منقوطة بعد التوقيع. يجب على كل فئة فرعية ملموسة تجاوز هذا التابع، وإلا رفض المُترجم تجميع تلك الفئة.

إنشاء الفئات الفرعية الملموسة

الفئة الملموسة هي ببساطة الفئة غير المجردة. وهي توفّر تنفيذات حقيقية لجميع التوابع المجردة التي ترثها:

public class Circle extends Shape { private final double radius; public Circle(double radius) { this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } } public class Rectangle extends Shape { private final double width; private final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double area() { return width * height; } }

يمكنك الآن استخدامهما:

public class Main { public static void main(String[] args) { Shape c = new Circle(5); Shape r = new Rectangle(4, 6); System.out.println(c.describe()); // I am a shape with area 78.53981633974483 System.out.println(r.describe()); // I am a shape with area 24.0 } }
تعدد الأشكال في العمل: يُخزَّن كلا الكائنَين كمراجع من نوع Shape. استدعاء describe() معرَّف مرةً واحدة في الفئة المجردة، غير أنه يستدعي داخليًا تنفيذ area() الصحيح في وقت التشغيل — هذا هو الإرسال الديناميكي الذي تعلّمته في الدرس الخامس.

توفير تنفيذ جزئي

من أكبر مزايا الفئات المجردة أنها تستطيع القيام ببعض العمل نيابةً عن الفئات الفرعية. لنفترض أن كل شكل له لون تتتبّعه بالطريقة ذاتها لجميع الأشكال:

public abstract class Shape { private final String colour; public Shape(String colour) { this.colour = colour; } public String getColour() { return colour; } // لا تزال الفئات الفرعية ملزمة بتوفير هذا التابع public abstract double area(); public String describe() { return colour + " shape, area = " + String.format("%.2f", area()); } }
public class Circle extends Shape { private final double radius; public Circle(String colour, double radius) { super(colour); // تسلسل الاستدعاء إلى منشئ الفئة المجردة this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } }

يُكتب حقل colour ومُحضِّره والتابع describe() مرةً واحدة ويُشاركون. الجزء الوحيد الذي يتغيّر حقًا — وهو area() — هو ما تتركه لكل فئة فرعية لتعرّفه.

قواعد يجب تذكّرها

  • يجب تصريح أي فئة تحتوي على تابع مجرد واحد على الأقل بأنها مجردة.
  • يجوز للفئة المجردة ألا تحتوي على أي تابع مجرد — وستظل غير قابلة للإنشاء، وهو ما قد يكون مفيدًا بذاته.
  • يجب تصريح الفئة الفرعية التي لا تُنفّذ جميع التوابع المجردة الموروثة بأنها مجردة بدورها.
  • يجوز للفئات المجردة أن تحتوي على منشئات وحقول وأعضاء ساكنة — فقط لا يمكن إنشاء كائنات منها مباشرةً.
خطأ شائع: نسيان تنفيذ كل التوابع المجردة في الفئة الفرعية الملموسة. سيُخبرك المُترجم بالتابع المفقود — اقرأ رسالة الخطأ بعناية، فهي تُسمّيه دائمًا.

الفئات المجردة مقابل الواجهات — نظرة أولى

ستتعلّم الواجهات (interfaces) قريبًا. في الوقت الحالي، الفرق المختصر هو:

  • الفئة المجردة يمكنها امتلاك حقول ومنشئات وتوابع مجردة وملموسة معًا. استخدمها حين تتشارك الفئات الفرعية حالة أو سلوكًا مشتركًا.
  • الواجهة تُعرّف عقدًا صرفًا — بلا حالة. استخدمها حين تريد وصف قدرة يمكن أن تتشاركها فئات غير مترابطة.

الخلاصة

تقع الفئات المجردة بين الفئة الملموسة الكاملة والواجهة الصرفة. تتيح لك تحديد ما يجب أن تفعله الفئات الفرعية إلزاميًا (التوابع المجردة)، مع توفير تنفيذ مشترك في الوقت ذاته (التوابع والحقول الملموسة). تمنع الكلمة المفتاحية abstract على الفئة الإنشاءَ العرضي للكائنات، وتُلزم الكلمة abstract على التابع كل فئة فرعية ملموسة بتوفير جسم حقيقي. هذا النمط هو أساس هيكل الأشكال الهندسية الذي ستبنيه في الدرس الأخير من هذه الوحدة.