Nullable مقابل Non-Nullable
جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 24 من 110.
الآن بعد أن فهمت سبب أهمية سلامة القيم الخالية (null safety)، دعنا نلقي نظرة على كيفية التصريح عن المتغيرات التي يمكن أن تحمل قيمة خالية (null). في لغة Dart، يمكنك جعل النوع قابلاً للقيمة الخالية (nullable) عن طريق إضافة ? بعد اسم النوع.
String name = 'Alice'; // غير قابل للقيمة الخالية - يجب أن يحتوي دائمًا على قيمة
String? nickname; // قابل للقيمة الخالية - يمكن أن يكون null أو String
print(name); // Alice
print(nickname); // nullالفرق الجوهري هو أن الأنواع غير القابلة للقيمة null تضمن وجود قيمة، بينما تسمح الأنواع القابلة للقيمة null صراحةً بغياب القيمة. ينطبق هذا التمييز على جميع الأنواع:
int count = 10; // يجب أن يحتوي على قيمة
int? optionalCount; // يمكن أن يكون null
List<String> items = []; // يجب أن تكون قائمة (يمكن أن تكون فارغة)
List<String>? maybeItems; // يمكن أن يكون null (لا توجد قائمة على الإطلاق)لاحظ أن القائمة الفارغة [] تختلف عن null. القائمة الفارغة موجودة ولكنها لا تحتوي على شيء؛ null تعني عدم وجود قائمة على الإطلاق.
يفرض المترجم هذه القواعد بصرامة. لا يمكنك تعيين null لمتغير غير قابل للقيمة null (non-nullable)، ولا يمكنك استخدام متغير قابل للقيمة null (nullable) حيث يُتوقع وجود متغير غير قابل للقيمة null دون التحقق أولاً من القيمة null:
String? maybeName = 'Bob';
// String definite = maybeName; // خطأ! لا يمكن تعيين قيمة قابلة للإلغاء (nullable) إلى قيمة غير قابلة للإلغاء (non-nullable)في الدرس القادم، ستتعلم المعاملات التي توفرها لغة Dart للعمل بأمان مع القيم التي تقبل القيمة الخالية (nullable values).
التحدي
سهللنقم ببناء نظام قائمة مشاهدة أفلام يوضح الفرق بين المعلومات المطلوبة والمعلومات الاختيارية باستخدام الأنواع القابلة للإلغاء (nullable) وغير القابلة للإلغاء (non-nullable).
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
movie.dart: قم بتعريف فئةMovieتمثل فيلماً في قائمة المشاهدة الخاصة بك. يجب أن يكون لكل فيلم عنوان وسنة إصدار (هذه مطلوبة دائماً)، ولكن قد لا تتوفر بعض التفاصيل الإضافية لكل فيلم. يجب أن تحتوي الفئة الخاصة بك على:- متغير
String titleغير قابل للإلغاء - يجب أن يكون لكل فيلم عنوان - متغير
int yearغير قابل للإلغاء - يجب أن يكون لكل فيلم سنة إصدار - متغير
String? directorقابل للإلغاء - قد يكون المخرج غير معروف - متغير
double? ratingقابل للإلغاء - لم يتم تقييم جميع الأفلام بعد - منشئ (constructor) يأخذ
titleوyearكمعاملات مطلوبة - دالة
displayInfo()تقوم بطباعة معلومات الفيلم، مع إظهار"Unknown"للمخرج المفقود و"Not rated"للتقييم المفقود
- متغير
main.dart: قم باستيراد فئة الفيلم الخاصة بك وإنشاء قائمة مشاهدة صغيرة توضح كلاً من بيانات الأفلام الكاملة وغير الكاملة:- قم بإنشاء فيلم بالعنوان
'Inception'والسنة2010، ثم قم بتعيين المخرج الخاص به إلى'Christopher Nolan'والتقييم إلى8.8 - قم بإنشاء فيلم بالعنوان
'The Matrix'والسنة1999، ثم قم بتعيين تقييمه فقط إلى8.7(اترك المخرج كـ null) - قم بإنشاء فيلم بالعنوان
'Upcoming Film'والسنة2025(اترك كلاً من المخرج والتقييم كـ null) - قم باستدعاء
displayInfo()لكل فيلم بالترتيب المذكور أعلاه
- قم بإنشاء فيلم بالعنوان
يجب أن تقوم الدالة displayInfo() بالطباعة بهذا التنسيق الدقيق:
[title] ([year])
Director: [director or "Unknown"]
Rating: [rating or "Not rated"]لاحظ كيف أن الحقول غير القابلة للإلغاء (title و year) مضمونة دائماً بوجود قيم، بينما الحقول القابلة للإلغاء (director? و rating?) يمكن أن تظل غير محددة. هذا يعكس سيناريوهات العالم الحقيقي حيث تكون بعض البيانات أساسية وبعضها اختيارياً.
المخرجات المتوقعة:
Inception (2010)
Director: Christopher Nolan
Rating: 8.8
The Matrix (1999)
Director: Unknown
Rating: 8.7
Upcoming Film (2025)
Director: Unknown
Rating: Not ratedورقة مرجعية
لجعل النوع قابلاً للقيمة الفارغة (nullable) في لغة Dart، أضف ? بعد اسم النوع:
String name = 'Alice'; // Non-nullable - must always have a value
String? nickname; // Nullable - can be null or a Stringينطبق هذا التمييز على جميع الأنواع:
int count = 10; // Must have a value
int? optionalCount; // Can be null
List<String> items = []; // Must be a list (can be empty)
List<String>? maybeItems; // Could be null (no list at all)القائمة الفارغة [] تختلف عن null. القائمة الفارغة موجودة ولكنها لا تحتوي على شيء؛ أما null فتعني عدم وجود قائمة على الإطلاق.
يفرض المترجم (compiler) قواعد سلامة القيم الفارغة (null safety) بصرامة. لا يمكنك تعيين null لمتغير غير قابل للقيمة الفارغة (non-nullable)، ولا يمكنك استخدام متغير قابل للقيمة الفارغة حيث يُتوقع وجود متغير غير قابل للقيمة الفارغة دون التحقق أولاً من القيمة الفارغة:
String? maybeName = 'Bob';
// String definite = maybeName; // Error! Can't assign nullable to non-nullableجرّب بنفسك
import 'movie.dart';
void main() {
// TODO: قم بإنشاء فيلم بالعنوان 'Inception' والسنة 2010
// ثم قم بتعيين director الخاص به إلى 'Christopher Nolan' و rating إلى 8.8
// TODO: قم بإنشاء فيلم بالعنوان 'The Matrix' والسنة 1999
// ثم قم بتعيين rating الخاص به فقط إلى 8.7 (اترك director كـ null)
// TODO: قم بإنشاء فيلم بالعنوان 'Upcoming Film' والسنة 2025
// اترك كل من director و rating كـ null
// TODO: قم باستدعاء displayInfo() لكل فيلم بالترتيب
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةالمكتبات والـ Importsمقدمة في الـ OOPالـ Classes مقابل الـ Objectsالكلمة المفتاحية thisالـ Methodsمتغيرات الـ Instanceأساسيات الـ Constructorمراجعة - آلة حاسبة بسيطة4أمان الـ Null
مقدمة في أمان الـ NullNullable مقابل Non-Nullableعاملا الـ ? و !الكلمة المفتاحية Late وأمان الـ Nullالعوامل المدركة للـ Nullأمان الـ Null في الـ Classesمراجعة - نظام ملف تعريف المستخدم7الفئات المجردة والواجهات
الفئات المجردةالدوال المجردةالواجهات في Dartالواجهات الضمنيةالتنفيذ مقابل التوسيعالواجهات المتعددةمراجعة - حاسبة الأشكال10المجموعات والأنماط العامة
نظرة عامة على List و Set و Mapالمجموعات آمنة النوعالفئات العامةالدوال العامةقيود الأنماط العامةIterable و Iteratorملخص - التخزين العام13مفاهيم OOP المتقدمة
التركيب مقابل الوراثة (Composition vs Inheritance)دوال التوسعة (Extension Methods)الأصناف القابلة للاستدعاء (Callable Classes)الأصناف المختومة (Sealed Classes - Dart 3)السجلات (Records - Dart 3)الأنماط والمطابقة (Patterns & Matching - 3.0)التعدادات مع الدوال (Enums with Methods)2المنشئات في Dart
المنشئ الافتراضيالمنشئات المسماةقوائم التهيئةالمنشئات الثابتةالمنشئات المصنعيةالمنشئات الموجهةملخص - بناء الأشكال5التغليف (Encapsulation)
الأعضاء العامة مقابل الخاصةاتفاقية البادئة _الخصوصية على مستوى المكتبةتعمق في Getters و Settersإخفاء المعلوماتمراجعة - سجلات الطلاب8Mixins
مقدمة إلى Mixinsإنشاء Mixinsاستخدام Mixins متعددةالكلمة المفتاحية on في MixinsMixin مقابل الوراثة (Inheritance)Mixin مقابل الواجهة (Interface)مراجعة - نظام الحيوانات (Animal System)3خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticحقول Final و Constمتغيرات Lateالـ Static Methods والحقولدوال الـ Getters والـ Settersمراجعة - مدير الحساب البنكي6الوراثة
أساسيات الوراثةالكلمة المفتاحية superإعادة تعريف الدوال (Method Overriding)الوسم @overrideالكلمة المفتاحية final Classالدوال البانية والوراثةمراجعة - هيكلية الموظفين9تعدد الأشكال
أساسيات تعدد الأشكالتعدد الأشكال عبر الـ Interfacesالتحقق من النوع أثناء التشغيلالمعاملات is و asالكلمة المفتاحية Covariantملخص - معالج الدفع12البرمجة الكائنية غير المتزامنة (Async OOP)
Futures و async/awaitأساسيات الـ Streamsمتحكمات الـ Streamsالـ Constructors غير المتزامنةالعمليات غير المتزامنة في دوال الكلاسمراجعة - جالب البيانات (Data Fetcher)15أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع