Constructor In OOP In Dart

🏗️ ما هو الـ Constructor؟

Constructor هو دالة خاصة داخل الكلاس تُنفذ تلقائيًا عند إنشاء كائن جديد (Object) من الكلاس، وتُستخدم بشكل أساسي لـ:

الفائدةالشرح
✅ تهيئة الخصائصإعطاء قيم ابتدائية للخصائص عند إنشاء الكائن.
✅ تنفيذ تعليمات مبدئيةيمكن تنفيذ أوامر بمجرد إنشاء الكائن.
✅ أولًا: Default Constructor (الباني الافتراضي)
🔹 التعريف:

هو الباني الذي يُنشأ تلقائيًا من قِبل Dart إذا لم تُنشئ أي باني بنفسك.

✨ مثال:
class Person {
  String name = 'غير معروف';
  int age = 0;

  void greet() {
    print('مرحبًا، اسمي $name وعمري $age');
  }
}

void main() {
  var p = Person(); // هنا Dart تستخدم الباني الافتراضي تلقائيًا
  p.greet(); // مرحبًا، اسمي غير معروف وعمري 0
}
Dart
✅ الملاحظات:
  • لا تكتب constructor بنفسك.
  • Dart تنشئه تلقائيًا.
  • يسمح بإنشاء الكائن بدون تمرير أي بيانات.
  • بمجرد ما تكتب أي Constructor بنفسك، Dart تحذف الـ default constructor تلقائيًا.
✅ ثانيًا: Parameterized Constructor (الباني المُعَمَّم أو المُخصص)
🔹 التعريف:

هو constructor تكتبه بنفسك ويأخذ باراميترات (parameters) لتهيئة الخصائص عند إنشاء الكائن.

🛠️ الطريقة الأولى: باستخدام this
class Person {
  String name;
  int age;

  // Constructor
  Person(this.name, this.age);
}

void main() {
  var p = Person('أحمد', 25);
  print(p.name); // أحمد
  print(p.age);  // 25
}
Dart
🔍 شرح:
  • this.name تعني أن المتغير الذي داخل الكلاس يُساوي القيمة القادمة من البراميتر.
  • Dart توفر طريقة مختصرة بدلاً من:
Person(String name, int age) {
  this.name = name;
  this.age = age;
}
Dart

🛠️ الطريقة الثانية: Constructor مُسمي (Named Constructor)

يُستخدم عندما نريد إنشاء أكثر من constructor في نفس الكلاس.

class Person {
  String name;
  int age;

  // Constructor عادي
  Person(this.name, this.age);

  // Constructor مسمى
  Person.fromName(this.name) {
    age = 0;
  }
}

void main() {
  var p1 = Person('ليلى', 22);
  var p2 = Person.fromName('خالد');

  print(p2.name); // خالد
  print(p2.age);  // 0
}
Dart
🛠️ الطريقة الثالثة: استخدام Initializer List (قائمة التهيئة)

إذا أردت إعداد الخصائص قبل تنفيذ جسم الـ constructor:

class Point {
  int x;
  int y;

  Point(int a, int b) : x = a, y = b {
    print('تم إنشاء النقطة');
  }
}

void main() {
  var p = Point(3, 4);
  print(p.x); // 3
  print(p.y); // 4
}
Dart
✅ ملخص سريع للفروق:
النوعيأخذ قيم؟تكتبه أنت؟الفائدة
Default❌ لا❌ لاإنشاء كائن بدون قيم.
Parameterized✅ نعم✅ نعمإدخال قيم مباشرة عند الإنشاء.
Named✅ نعم✅ نعمإنشاء كائن بأكثر من طريقة.
Initializer List✅ نعم✅ نعمتهيئة الخصائص قبل تنفيذ الجسم.
✅ فوائد استخدام Parameterized Constructor:
الفائدةالشرح
🎯 الدقةيجبرك على إدخال البيانات المطلوبة عند إنشاء الكائن.
🔒 الأمانيمنع الكائن من أن يُنشأ دون بيانات ضرورية.
🔁 التخصيصيمكنك إنشاء أكثر من باني لتناسب حالات مختلفة.
⛏️ تقليل الأخطاءيمنع نسيان تهيئة الخصائص.
🧼 الكود أنظفبدل كتابة خصائص وقيم بعد إنشاء الكائن، تكون جاهزة من البداية.
✅ مثال تطبيقي عملي:
class Student {
  String name;
  int grade;

  // Constructor
  Student(this.name, this.grade);

  void showInfo() {
    print('الطالب: $name - الدرجة: $grade');
  }
}

void main() {
  var s1 = Student('علي', 95);
  var s2 = Student('منى', 88);

  s1.showInfo(); // الطالب: علي - الدرجة: 95
  s2.showInfo(); // الطالب: منى - الدرجة: 88
}
Dart
✅ ملاحظة هامة:

إذا كتبت Constructor خاص، Dart لا تنشئ Constructor افتراضي لك.
وبالتالي، إذا أردت استخدام الاثنين، يجب أن تكتب كلاهما بنفسك.

✅ ما هو Named Constructor؟

Named Constructor هو نوع من الـ constructors نُسميه باسم مخصص داخل الكلاس، ويُستخدم عندما:

  • نريد أكثر من طريقة مختلفة لإنشاء الكائن.
  • نحتاج تهيئة بعض الخصائص دون غيرها.
  • نرغب بمرونة أكبر من constructor واحد فقط.
✅ تركيب الـ Named Constructor:
class ClassName {
  ClassName.namedConstructorName() {
    // كود التهيئة
  }
}
Dart
✅ مثال بسيط:
class Car {
  String brand;
  int year;

  // Constructor الأساسي
  Car(this.brand, this.year);

  // Named Constructor
  Car.fromOldModel(String brandName) {
    brand = brandName;
    year = 2000;
  }

  void info() {
    print('السيارة: $brand - سنة الصنع: $year');
  }
}

void main() {
  var car1 = Car('Toyota', 2023);
  var car2 = Car.fromOldModel('Nissan');

  car1.info(); // السيارة: Toyota - سنة الصنع: 2023
  car2.info(); // السيارة: Nissan - سنة الصنع: 2000
}
Dart
✅ لماذا نستخدم Named Constructors؟
السببالشرح
🔁 تعدد الطرقلإنشاء الكائن بأكثر من طريقة حسب الحالة.
🔒 وضوح النيةالاسم يوضح الغرض (مثلاً: fromJson، fromDatabase، fromName).
🛠️ تسهيل التحويلاتيمكنك تحويل البيانات من مصادر مختلفة لكائنات.
🧱 تنظيم الكوديجعل الكود أكثر ترتيبًا ومرونة.
✅ مثال عملي آخر: تحويل JSON إلى كائن
class User {
  String name;
  int age;

  // Constructor عادي
  User(this.name, this.age);

  // Named Constructor لتحويل JSON إلى كائن
  User.fromJson(Map<String, dynamic> json)
      : name = json['name'] ?? 'غير معروف',
        age = (json['age'] ?? 0).toInt();

  // دالة لعرض المعلومات
  void show() {
    print('👤 الاسم: $name - العمر: $age');
  }
}

void main() {
  // بيانات JSON على شكل List من الخرائط
  List<Map<String, dynamic>> usersData = [
    {'name': 'ياسين', 'age': 30},
    {'name': 'سارة', 'age': 25},
    {'name': 'أحمد'}, // العمر ناقص
    {'age': 40},      // الاسم ناقص
    {},               // الكل ناقص
  ];

  // تحويل القائمة إلى قائمة من كائنات User
  List<User> users = usersData.map((json) => User.fromJson(json)).toList();

  // عرض كل المستخدمين
  for (var user in users) {
    user.show();
  }
}
Dart
✅ هل يمكن استخدام أكثر من Named Constructor في نفس الكلاس؟

نعم بكل تأكيد ✅

🔸 مثال:
class Shape {
  double width;
  double height;

  // Constructor أساسي
  Shape(this.width, this.height);

  // مربع
  Shape.square(double size) {
    width = size;
    height = size;
  }

  // مستطيل
  Shape.rectangle(this.width, this.height);

  void info() {
    print('العرض: $width - الارتفاع: $height');
  }
}

void main() {
  var s1 = Shape.square(10);
  var s2 = Shape.rectangle(10, 20);

  s1.info(); // العرض: 10 - الارتفاع: 10
  s2.info(); // العرض: 10 - الارتفاع: 20
}
Dart
✅ مقارنة بين Constructors المختلفة:
النوعالاسميستخدم this؟يأخذ قيم؟التخصيص
العاديClassName(...)✅ غالبًا✅ نعممحدود
NamedClassName.name(...)✅ غالبًا✅ نعممرن وأكثر قابلية للتخصيص
✅ ملاحظات مهمة:
  1. يمكن استخدام أكثر من Named Constructor في نفس الكلاس.
  2. لا يوجد Overloading في Dart مثل بعض اللغات الأخرى، لذلك الـ Named Constructors هو الحل الوحيد لعدة طرق للتهيئة.
  3. يمكن استدعاء الـ Named Constructor بنفس طريقة أي Constructor
var obj = ClassName.namedConstructorName(...);
Dart
✅ تمرين لك (اختياري):

اكتب كلاس اسمه Student، واجعل فيه:

  • Constructor عادي.
  • Named Constructor اسمه fromMap يستقبل Map يحتوي على name وgrade.
  • دالة info() تطبع بيانات الطالب.
✅ كود التمرين الكامل:
class Student {
  String name;
  double grade;

  // Constructor العادي
  Student(this.name, this.grade);

  // Named Constructor: fromMap
  Student.fromMap(Map<String, dynamic> data) {
    name = data['name'];
    grade = data['grade'].toDouble(); // تأكدنا أنها double
  }

  // دالة لطباعة المعلومات
  void info() {
    print('اسم الطالب: $name');
    print('الدرجة: $grade');
  }
}

void main() {
  // استخدام الـ Constructor العادي
  Student s1 = Student('أحمد', 92.5);
  s1.info();

  print('---');

  // استخدام الـ Named Constructor fromMap
  Map<String, dynamic> studentData = {
    'name': 'سارة',
    'grade': 87
  };

  Student s2 = Student.fromMap(studentData);
  s2.info();
}
Dart
✅ ماذا يفعل الكود؟
السطرما يفعله
Student(this.name, this.grade);Constructor عادي يُمرر القيم مباشرة.
Student.fromMap(...)Constructor مسمى يأخذ Map (مثل من JSON أو قاعدة بيانات) ويحوله إلى كائن.
s1.info() و s2.info()تطبع بيانات الطالب في كل حالة.
✅ المخرجات:
اسم الطالب: أحمد
الدرجة: 92.5
---
اسم الطالب: سارة
الدرجة: 87.0
Dart
✅ الفوائد التعليمية:
  • تعلمت الفرق بين constructor العادي والمسمى.
  • كيفية استقبال Map وتحويله إلى كائن.
  • تنظيم الكود وتقسيم طرق إنشاء الكائنات بحسب الحاجة.