الـ Sealed Classes (Java 17+)
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 52 من 87.
في Java التقليدية، عندما تنشئ class، يمكن لأي class أخرى أن ترث منها (ما لم تضع عليها العلامة final). ولكن ماذا لو أردت السماح بالوراثة، ولكن ليس من أي شخص؟ قدّمت Java 17 الفئات المختومة لتمنحك تحكمًا دقيقًا في الفئات التي يمكنها أن ترث من فئتك.
يُصرّح الصنف المختوم بشكل صريح بالفئات الفرعية المسموح بها باستخدام الكلمتين المفتاحيتين sealed وpermits:
sealed abstract class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}
final class Circle extends Shape {
double radius;
double area() { return Math.PI * radius * radius; }
}
final class Rectangle extends Shape {
double width, height;
double area() { return width * height; }
}
final class Triangle extends Shape {
double base, height;
double area() { return 0.5 * base * height; }
}يمكن فقط لـ Circle وRectangle وTriangle أن ترث من Shape. وأي class أخرى تحاول أن ترث منها ستؤدي إلى حدوث خطأ في الترجمة.
يجب أن تستخدم كل فئة فرعية مسموح بها واحدًا من ثلاثة مُعدِّلات: final (لا مزيد من التوسعة)، أو sealed (تستمر سلسلة التقييد)، أو non-sealed (يفتح المجال للتوسعة غير المقيّدة):
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // لا يمكن تمديده
non-sealed class Truck extends Vehicle { } // يمكن لأي شخص تمديد Truckتكون sealed classes قوية بشكل خاص مع pattern matching في تعبيرات switch، إذ يعرف المصرّف جميع الأنواع الفرعية الممكنة ويمكنه التحقق من الاكتمال. وهي مثالية عند نمذجة مجموعة ثابتة من الأنواع في مجال معيّن، مثل طرق الدفع أو أنواع الاستجابات أو الأشكال الهندسية.
التحدي
سهللنبنِ نظام إشعارات يوضّح استخدام الفئات المختومة من خلال تقييد أنواع الإشعارات التي يمكن أن توجد في تطبيقك. ستنشئ تسلسلاً هرميًا يُسمح فيه بأنواع محددة فقط من الإشعارات، ويتولى كل نوع عملية التسليم بطريقة مختلفة.
ستنظّم الشيفرة عبر أربعة ملفات:
Notification.java: أنشئ فئةabstractمختومة تعمل كفئة أساسية لجميع الإشعارات. يجب أن تسمح بثلاث فئات فرعية بالضبط:EmailNotificationوSMSNotificationوPushNotification. يجب أن تحتوي الفئة المختومة على الحقل المحميmessage(من النوع String)، وبانٍ لتهيئته، وgetter للرسالة، وطريقة مجردةdeliver()تُرجع قيمة من النوع String تصف كيفية إرسال الإشعار.EmailNotification.java: أنشئ فئةfinalتمتد من Notification. تحتوي EmailNotification على حقل خاص إضافي هوrecipient(من النوع String) لعنوان البريد الإلكتروني. يجب أن يستقبل بانِيها كلاً من الرسالة والمستلم، مع استدعاء بانِي الفئة الأصل باستخدامsuper(). يجب أن تُرجع الطريقةdeliver():Sending email to [recipient]: [message]SMSNotification.java: أنشئ فئةnon-sealedتمتد من Notification. يتيح ذلك للفئات الأخرى أن تمتد منها إذا لزم الأمر في المستقبل. تحتوي SMSNotification على حقل خاص إضافي هوphoneNumber(من النوع String). يستقبل بانِيها الرسالة ورقم الهاتف. يجب أن تُرجع الطريقةdeliver():Sending SMS to [phoneNumber]: [message]PushNotification.java: أنشئ فئةfinalتمتد من Notification. تحتوي PushNotification على حقل خاص إضافي هوdeviceId(من النوع String). يستقبل بانِيها الرسالة ومعرّف الجهاز. يجب أن تُرجع الطريقةdeliver():Sending push to device [deviceId]: [message]Main.java: اجمع نظام الإشعارات لديك! ستتلقى أربعة مدخلات: رسالة (String)، وعنوان بريد إلكتروني (String)، ورقم هاتف (String)، ومعرّف جهاز (String).أنشئ مثيلاً واحدًا من كل نوع من أنواع الإشعارات باستخدام الرسالة نفسها، ولكن مع معلومات الاتصال الخاصة بكل منها. خزّن الأنواع الثلاثة جميعًا في مصفوفة من النوع
Notification[]لتوضيح تعدد الأشكال باستخدام الفئات المختومة. ثم تكرّر عبر المصفوفة وتطبع نتيجة استدعاءdeliver()على كل إشعار.
ستتلقى أربعة مدخلات بالترتيب التالي: الرسالة، وعنوان البريد الإلكتروني، ورقم الهاتف، ومعرّف الجهاز.
لاحظ كيف تقيّد الفئة المختومة التسلسل الهرمي: يمكن للفئات الثلاث المسموح بها فقط أن تمتد من Notification. يجب أن تعلن كل فئة فرعية عن نفسها باستخدام final أو sealed أو non-sealed. يمنحك ذلك تحكمًا كاملاً في التسلسل الهرمي للأنواع، مع الاستمرار في تمكين تعدد الأشكال!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة المدخلات الأربعة
String message = scanner.nextLine();
String email = scanner.nextLine();
String phoneNumber = scanner.nextLine();
String deviceId = scanner.nextLine();
// TODO: إنشاء إشعار واحد من كل نوع باستخدام نفس الـ message
// - EmailNotification باستخدام message و email
// - SMSNotification باستخدام message و phoneNumber
// - PushNotification باستخدام message و deviceId
// TODO: تخزين جميع الإشعارات الثلاثة في مصفوفة Notification[]
// هذا يوضح مفهوم تعدد الأشكال (polymorphism) مع sealed classes
// TODO: التكرار عبر المصفوفة وطباعة نتيجة deliver()
// لكل إشعار
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت