Sealed Class هو نوع خاص من الفئات في Kotlin يحدد تسلسل الوراثة بمجموعة ثابتة من الأنواع الفرعية. جميع الأنواع الفرعية تُعلن في نفس الملف ويكون المترجم على علم بها، مما يسمح باستخدام كتلة when شاملة بدون فرع else إلزامي. وفقًا Kotlin Docs, 2026، الفئات المختومة هي آلية رئيسية لتمثيل التسلسلات الهرمية المحدودة مثل الحالات وأنواع الأخطاء وأحداث واجهة المستخدم.
أهم النقاط
Sealed Class (فئة مختومة) هي فئة في Kotlin موسومة بالمُعدل sealed. وهي تحدد تسلسلاً هرمياً محدوداً للأنواع: جميع الأنواع الفرعية المحتملة مدرجة في نفس الملف والمترجم يعرف كل منها. وهذا يمييز sealed class عن الفئة المفتوحة العادية التي يمكن تعريف أنواعها الفرعية في أي مكان.
الهدف الرئيسي من sealed class هو تمثيل آمن من حيث النوع لمجموعة محدودة من الخيارات. يمكن لكل نوع فرعي أن يكون له هيكل بيانات خاص به، مما يجعل sealed class أكثر مرونة من enum. في وقت التشغيل، sealed class هي فئة مجردة عادية؛ يفرض المترجم قيودًا فقط في وقت الترجمة.
Sealed class مفيد بشكل خاص في هندسة تطبيقات Android: حالات واجهة المستخدم، نتائج طلبات الشبكة، أحداث التنقل الشبيهة بـ Intents، وبالطبع التسلسلات الهرمية للأخطاء — كلها حالات استخدام نموذجية.
في وقت الترجمة، يتم تحسين sealed class إلى جدول قفز لتعبيرات when، مما يجعله أكثر كفاءة من سلاسل if-else. وعند دمجه مع data class، يمكن لكل نوع فرعي أن يحتوي ليس فقط على حالة ولكن أيضًا على طرق، مما يسمح ببناء نماذج مجال موثقة ذاتيًا بدون كود شائع.
Sealed class فعال أيضًا في تمثيل آلات الحالة في التطبيقات المحمولة. كل حالة هي نوع فرعي منفصل بمعلمات فريدة، ويتم التحكم في التحولات بين الحالات عبر تعبير when. يضمن المترجم معالجة جميع الحالات الممكنة، مما يستبعد أخطاء وقت التشغيل عند تغيير حالة واجهة المستخدم أو منطق الأعمال.
غالبًا ما يخلط مطورو Kotlin المبتدئون بين sealed class و enum، حيث أن كلاهما يحد من مجموعة القيم. ومع ذلك، هناك فرق جوهري: enum هو مجموعة من الثوابت من نفس النوع، بينما sealed class هو تسلسل هرمي لأنواع مختلفة.
Enum هو الأمثل عندما تكون جميع الخيارات ثوابت بدون بنية إضافية. على سبيل المثال، أيام الأسبوع، حالات الطلب، أو أنواع الإجراءات بدون معلمات. كل قيمة enum هي singleton باسم ثابت.
Sealed class مطلوب عندما يكون لكل خيار بياناته الخاصة. على سبيل المثال، خطأ الشبكة يحتوي على رمز الاستجابة، خطأ التحليل يحتوي على تفاصيل، وخطأ التفويض يحتوي على رسالة. كل نوع فرعي من sealed class هو نوع منفصل بحقول فريدة.
// Enum — جميع الخيارات من نوع واحد
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — كل خيار ببياناته الخاصة
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
منذ Kotlin 1.5، أصبح من الممكن تعريف sealed interface. وهذا يوسع مفهوم الختم للواجهات: sealed interface لديها أيضًا مجموعة ثابتة من التطبيقات ولكنها تدعم الوراثة المتعددة.
Sealed interface مفيد عندما تحتاج الأنواع الفرعية إلى تطبيق عدة عقود في وقت واحد. على سبيل المثال، يمكن أن يكون حدث واجهة المستخدم قابلًا للنقر والتتبع في نفس الوقت. مع sealed class، سيتعين عليك اختيار فئة أساسية واحدة؛ مع sealed interface، يقوم النوع الفرعي بتطبيق كليهما.
Sealed class هي فئة، لذلك يمكن لكل نوع فرعي أن يكون له أب واحد فقط. Sealed interface يحل هذه المشكلة لكنه لا يمكنه احتواء حالة. يعتمد الاختيار بينهما على المهمة: إذا كانت هناك حاجة لمنطق مشترك مع حقول — استخدم sealed class؛ إذا كانت هناك حاجة لمرونة العقود — استخدم sealed interface.
sealed interface ScreenEvent {
data class Refresh(val force: Boolean) : ScreenEvent
data class Navigate(val route: String) : ScreenEvent
data class ShowError(val toast: String) : ScreenEvent
}
sealed interface AnalyticsEvent {
val name: String
val params: Map<String, Any>
}
// النوع الفرعي يطبق كلتا الواجهتين
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
أحد الاستخدامات الرئيسية لـ sealed class في تطوير التطبيقات المحمولة هو التسلسلات الهرمية الآمنة من حيث النوع للأخطاء. بدلاً من رمي استثناءات من أنواع مختلفة أو استخدام Exception عام، يجمع sealed class جميع أخطاء المجال الممكنة في نوع واحد.
أنشئ sealed class باسم DomainError واذكر جميع أنواع الفشل كأنواع فرعية. يحتوي كل نوع فرعي فقط على البيانات ذات الصلة بهذا النوع المعين من الخطأ. يضمن المترجم أنك عند معالجة الخطأ لن تنسى أي خيار.
لنفكر في تطبيق مع تفويض حيث توجد سيناريوهات فشل مختلفة: كلمة مرور خاطئة، حساب محظور، مشكلة في الخادم. يجمعها sealed class في نوع واحد بمعالجة شاملة.
sealed class AuthError {
data class InvalidCredentials(
val attempts: Int
) : AuthError()
data class AccountBlocked(
val until: Long
) : AuthError()
data class NetworkFailure(
val cause: Throwable
) : AuthError()
object ServerError : AuthError()
}
fun handleError(error: AuthError): String = when (error) {
is AuthError.InvalidCredentials ->
"المحاولات المتبقية: ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"الوصول محظور حتى ${Date(error.until)}"
is AuthError.NetworkFailure ->
"تحقق من الاتصال: ${error.cause.localizedMessage}"
AuthError.ServerError ->
"الخادم غير متاح مؤقتًا"
}
Sealed class أصبح أداة قياسية في هندسة تطبيقات Android. دعونا نلقي نظرة على ثلاثة أنماط رئيسية حيث يكون sealed class لا غنى عنه في تطوير التطبيقات المحمولة.
كما تجدر الإشارة إلى استخدام sealed class في Clean Architecture. كل طبقة (data, domain, presentation) تستخدم sealed class لأنواع الأخطاء الخاصة بها، وتقوم المحولات (mappers) بتحويل sealed class إلى آخر. على سبيل المثال، DataError من طبقة البيانات يتم تحويله إلى DomainError لمنطق الأعمال، ثم إلى UiState لطبقة العرض. هذا يحافظ على أمان الأنواع في جميع مستويات التطبيق ويضمن عدم بقاء أي خطأ دون معالجة.
اختبار sealed class يتطلب نهجًا خاصًا، حيث أن كل نوع فرعي هو نوع منفصل بحالته الخاصة. يُوصى بكتابة اختبارات معلمة تمر عبر جميع الأنواع الفرعية لـ sealed class. وهذا يضمن أن تعبيرات when تغطي جميع الخيارات، بما في ذلك الجديدة المضافة عند توسيع التسلسل الهرمي.
لـ اختبارات واجهة المستخدم، sealed class كـ UiState يسمح بالتحقق من عرض كل حالة: Loading يُظهر spinner، Content يُظهر البيانات، Error يُظهر رسالة الخطأ. بما أن sealed class محدود، فإن التغطية الاختبارية لجميع الحالات تعطي ثقة كاملة في صحة منطق واجهة المستخدم.
على الرغم من بساطة المفهوم، فإن المطورين يرتكبون أخطاء بانتظام عند تصميم التسلسلات الهرمية لـ sealed class. دعونا نلقي نظرة على المشاكل الرئيسية وكيفية تجنبها.
الأسئلة الشائعة
نعم، يمكن لـ sealed class أن تحتوي على طرق مجردة، وكل نوع فرعي ملزم بتطبيقها. هذا مفيد عندما يجب أن توفر جميع الخيارات واجهة مشتركة ولكن بمنطق تنفيذ مختلف.
في Java 17+، تم تقديم الفئات والواجهات المختومة بالمُعدل sealed. Android يدعم حالياً Java 17 جزئياً، لكن في مشاريع Kotlin، sealed class متاح منذ Kotlin 1.0 بدون قيود.
نعم، يمكن أن تكون sealed class نوعاً فرعياً من أخرى. يبقى التسلسل الهرمي لـ sealed classes محدوداً: المترجم يعرف جميع الأنواع الفرعية في كل مستوى. هذا يسمح ببناء تصنيفات مفصلة للأخطاء.
Sealed class لا تخلق عبئاً إضافياً في وقت التشغيل. يقوم المترجم بتحسين تعبيرات when مع sealed classes إلى جداول قفز (tableswitch)، وهي أسرع من سلاسل if-else. الأداء مطابق لـ enum.
يتم اختبار كل نوع فرعي من sealed class بشكل منفصل. بما أن sealed class محدود، يمكن كتابة اختبار معلمة يمر عبر جميع الخيارات. وهذا يعطي تغطية كاملة لفروع كتلة when.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا