تخطَّ إلى المحتوى

الكلاسات في Dart: البرمجة كائنية التوجه

الدرس 7 من 14 · Dart

في Dart كل شيء كائن (Object): الأرقام والنصوص والقوائم، وحتى كل Widget في Flutter. والكائنات تُصنع من كلاسات (Classes). في هذا الدرس ستتعلم كيف تصمم كلاساتك الخاصة، وستفهم لماذا يُعد هذا الدرس المفتاح الحقيقي لفهم Flutter.

الكلاس مثل القالب أو المخطط الهندسي، والكائن هو الشيء الحقيقي المصنوع منه. مخطط سيارة واحد يُصنع منه آلاف السيارات، لكل منها لون ورقم لوحة مختلف.

class Student {
String name;
int age;
Student(this.name, this.age);
void introduce() {
print('أنا $name وعمري $age سنة');
}
}
void main() {
var s1 = Student('سارة', 14);
var s2 = Student('عمر', 15);
s1.introduce();
s2.introduce();
s2.age = 16;
print(s2.age);
}
الناتج
أنا سارة وعمري 14 سنة
أنا عمر وعمري 15 سنة
16
  • الخصائص (Properties): name و age، وهي بيانات كل كائن.
  • الدوال (Methods): introduce، وهي ما يستطيع الكائن فعله.
  • المُنشئ (Constructor): Student(this.name, this.age)، الدالة التي تصنع الكائن. والصيغة this.name اختصار ذكي في Dart يعني «ضع القيمة الأولى في الخاصية name مباشرة».
  • اسم الكلاس بحرف كبير Student، وكل كائن مستقل: تغيير عمر عمر لم يؤثر على سارة.

المُنشئ بالمعاملات المسماة

Section titled “المُنشئ بالمعاملات المسماة”

تماماً كما تعلمت في درس الدوال، يمكن أن يستخدم المُنشئ معاملات مسماة، وهذا الأسلوب المعتاد في Flutter:

class Product {
final String name;
final double price;
final int quantity;
Product({required this.name, required this.price, this.quantity = 1});
double get total => price * quantity;
}
void main() {
var p = Product(name: 'كتاب', price: 60, quantity: 3);
print(p.total);
}
الناتج
180.0

لاحظ أن الخصائص final: بعد إنشاء المنتج لا تتغير بياناته. والكائنات التي لا تتغير أأمن وأسهل في التتبع، و Flutter يعتمد عليها كثيراً.

double get total => price * quantity; تبدو كدالة، لكنها تُقرأ كخاصية بدون أقواس: p.total وليس p.total(). استخدمها للقيم المحسوبة من خصائص أخرى.

يمكن أن يكون للكلاس أكثر من طريقة لإنشائه، كل واحدة باسم:

class Product {
// ... نفس الخصائص والمُنشئ السابق
Product.free(this.name)
: price = 0,
quantity = 1;
}
void main() {
print(Product.free('ملصق').total);
}
الناتج
0.0

Product.free طريقة مختصرة لإنشاء منتج مجاني. والجزء بعد النقطتين : يسمى قائمة التهيئة (Initializer List)، ويعطي الخصائص قيمها قبل اكتمال الكائن.

في Dart، أي اسم يبدأ بشرطة سفلية _ يكون خاصاً (Private)، لا يُرى من خارج الملف:

class BankAccount {
final String owner;
double _balance = 0;
BankAccount(this.owner);
double get balance => _balance;
void deposit(double amount) {
if (amount <= 0) {
print('المبلغ يجب أن يكون أكبر من صفر');
return;
}
_balance += amount;
}
}
void main() {
var acc = BankAccount('منى');
acc.deposit(500);
acc.deposit(-20);
print(acc.balance);
}
الناتج
المبلغ يجب أن يكون أكبر من صفر
500.0

الرصيد _balance محمي: لا يستطيع أحد من خارج الكلاس كتابة acc._balance = 1000000. الطريقة الوحيدة لتغييره هي deposit التي تتحقق من المبلغ أولاً. أما القراءة فمتاحة عبر الـ getter balance. هذه الفكرة اسمها التغليف (Encapsulation)، وهي مثل ماكينة الصراف: ترى رصيدك وتودع فيه، لكن لا تستطيع فتح الخزنة بيدك.

كلاس يمكن أن يرث كل خصائص ودوال كلاس آخر، ثم يضيف أو يعدّل عليها:

class Animal {
final String name;
Animal(this.name);
void speak() {
print('$name يصدر صوتاً');
}
}
class Cat extends Animal {
Cat(super.name);
@override
void speak() {
print('$name يقول: مياو');
}
}
void main() {
var animals = [Animal('الحصان'), Cat('مشمش')];
for (var a in animals) {
a.speak();
}
}
الناتج
الحصان يصدر صوتاً
مشمش يقول: مياو
  • extends Animal: القطة نوع من الحيوانات، فترث name و speak.
  • super.name: مرّر الاسم لمُنشئ الأب.
  • @override: نعيد كتابة دالة موروثة بسلوك خاص.

عند طباعة كائن، تطبع Dart افتراضياً شيئاً مثل Instance of 'Product'. أعد كتابة toString لتطبع شيئاً مفيداً:

@override
String toString() => 'Product($name, $price)';

الآن print(p) تطبع Product(كتاب, 60.0).

صمم كلاس Book فيه: العنوان والمؤلف وعدد الصفحات (معاملات مسماة، والمؤلف إجباري)، و getter اسمه isLong يُرجع true إذا تجاوزت الصفحات 300، ودالة describe تطبع وصف الكتاب. ثم أنشئ كتابين واستدعِ describe لكل منهما.

تحدٍّ: أنشئ EBook extends Book يضيف خاصية حجم الملف، ويعيد كتابة describe.

  • نسيان this في المُنشئ المختصر: Student(name, age); لا يعطي الخصائص قيماً. الصحيح Student(this.name, this.age);.
  • خصائص بدون قيم: خاصية String name; بلا مُنشئ يملؤها، يرفضها المحرر بسبب Null Safety.
  • نسيان @override: الكود يعمل، لكن المحرر ينبهك، وكتابتها توضح أنك تعيد كتابة دالة موروثة عن قصد.

سؤال

ما معنى أن يبدأ اسم الخاصية بشرطة سفلية مثل _balance؟

سؤال

كيف تقرأ getter اسمه total من كائن اسمه p؟

  • الكلاس قالب، والكائن نسخة حقيقية منه لها بياناتها الخاصة.
  • this.name في المُنشئ اختصار للإسناد، والمعاملات المسماة مع required أسلوب Flutter.
  • get للقيم المحسوبة، والمُنشئ المسمى لطرق إنشاء إضافية.
  • _ تجعل الأسماء خاصة لحماية البيانات.
  • extends للوراثة و @override لإعادة الكتابة، وهذا هو أساس Widgets في Flutter.