Dart: البرمجة الوصفية والانعكاس في Dart — تعليق الكود

آخر تحديث: 2026-08-26

البرمجة الوصفية هي كتابة كود يكتب كوداً — أتمتة العمل المتكرر بحيث يمكن للمطورين التركيز على منطق الأعمال.

1. ما ستتعلمه


2. قصة مطور حقيقية

(1) نقطة الألم: كود التسلسل اليدوي يمثل 60% من جهد التطوير

يحتوي خط أنابيب البيانات الخاص ببوب على 20 فئة نموذج بيانات، كل منها يتطلب طرق fromJson/toJson. كتابة كود التسلسل يدوياً لـ 20 فئة استغرقت 3 أيام وكانت عرضة للأخطاء — خطأ إملائي واحد في اسم حقل تسبب في فشل تحليل 100,000 سجل.

(2) حل توليد الكود

اختارت Dart توليد الكود بدلاً من الانعكاس. من خلال تعليق فئات النماذج، يقوم build_runner تلقائياً بتوليد كود التسلسل.

DART
import 'package:json_annotation/json_annotation.dart';
part 'order.g.dart';

@JsonSerializable()
class Order {
  final String id;
  final double amount;

  Order({required this.id, required this.amount});

  factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
  Map<String, dynamic> toJson() => _$OrderToJson(this);
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

(3) الفوائد


3. التعليقات التوضيحية

(1) تعريف واستخدام التعليقات التوضيحية

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

: تعليقات توضيحية مخصصة

DART
// تعريف تعليق توضيحي مخصص
class Column {
  final String name;
  final bool nullable;
  final String? defaultValue;

  const Column({
    required this.name,
    this.nullable = false,
    this.defaultValue,
  });
}

class Table {
  final String name;
  const Table(this.name);
}

// تطبيق التعليقات التوضيحية على فئة
@Table('orders')
class Order {
  @Column(name: 'order_id')
  final String id;

  @Column(name: 'amount', nullable: false)
  final double amount;

  @Column(name: 'status', defaultValue: 'pending')
  final String status;

  Order({required this.id, required this.amount, this.status = 'pending'});
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

: قراءة التعليقات التوضيحية باستخدام الانعكاس (VM فقط)

DART
import 'dart:mirrors';

// قراءة التعليقات التوضيحية باستخدام الانعكاس (VM فقط!)
void printTableInfo(Type type) {
  final classMirror = reflectClass(type);

  // قراءة التعليقات التوضيحية على مستوى الفئة
  for (final metadata in classMirror.metadata) {
    if (metadata.reflectee is Table) {
      final table = metadata.reflectee as Table;
      print('Table: ${table.name}');
    }
  }

  // قراءة التعليقات التوضيحية على مستوى الحقول
  classMirror.declarations.forEach((key, declaration) {
    if (declaration is VariableMirror) {
      for (final metadata in declaration.metadata) {
        if (metadata.reflectee is Column) {
          final column = metadata.reflectee as Column;
          print('  ${declaration.simpleName}: ${column.name} '
              '(nullable: ${column.nullable}, default: ${column.defaultValue})');
        }
      }
    }
  });
}

void main() {
  printTableInfo(Order);
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.
⚠️ ملاحظة: dart:mirrors متاح فقط على Dart VM. غير مدعوم في وضع إصدار Flutter (تجميع AOT) أو على الويب.


4. API انعكاس dart:mirrors

(1) نظرة عامة على قدرات الانعكاس

100%
graph TD
  A[البرمجة الوصفية] --> B[انعكاس dart:mirrors]
  A --> C[تعليقات توضيحية + توليد الكود]
  B --> B1[مرونة وقت التشغيل]
  B --> B2[VM فقط / AOT غير متاح]
  C --> C1[توليد وقت الترجمة]
  C --> C2[متوافق مع AOT / ودود مع Flutter]
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

: استخدام API الانعكاس

DART
import 'dart:mirrors';

class Product {
  final String name;
  final double price;
  String category;

  Product({required this.name, required this.price, this.category = 'General'});

  String formatPrice() => '\$${price.toStringAsFixed(2)} USD';

  double applyDiscount(double rate) => price * (1 - rate);
}

void reflectOnProduct() {
  final mirror = reflectClass(Product);

  // سرد جميع طرق المثيل
  print('Methods:');
  mirror.instanceMembers.forEach((name, member) {
    if (member is MethodMirror && !member.isConstructor && !member.isStatic) {
      print('  $name: ${member.returnType.reflectedType}');
    }
  });

  // إنشاء مثيل عبر الانعكاس
  final instance = mirror.newInstance(
    Symbol(''),
    [],
    {#name: 'Laptop', #price: 1299.99, #category: 'Electronics'},
  );

  // استدعاء طريقة عبر الانعكاس
  final formatted = instance.invoke(#formatPrice, []);
  print('Formatted: ${formatted.reflectee}');

  final discounted = instance.invoke(#applyDiscount, [0.1]);
  print('Discounted: \$${discounted.reflectee.toStringAsFixed(2)} USD');
}

void main() {
  reflectOnProduct();
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.
قدرة الانعكاس API الوصف
الحصول على معلومات الفئة reflectClass(Type) اسم الفئة، الطرق، الحقول
إنشاء مثيل newInstance() إنشاء ديناميكي
استدعاء طريقة invoke() استدعاء ديناميكي للطريقة
قراءة حقل getField() وصول ديناميكي للخاصية
قراءة التعليقات التوضيحية .metadata الحصول على البيانات الوصفية

5. توليد الكود مقابل الانعكاس

(1) المقارنة والمقايضات

البُعد انعكاس dart:mirrors توليد الكود (build_runner)
وقت التشغيل مرن، قرارات وقت التشغيل محدد في وقت الترجمة
متوافق مع AOT غير متوافق متوافق
دعم Flutter غير متاح في وضع الإصدار مدعوم في جميع الأوضاع
دعم الويب غير متاح مدعوم
الأداء عبء وقت التشغيل بدون عبء وقت تشغيل
تجربة المطور لا حاجة لخطوة توليد يتطلب خطوة build_runner
تصحيح الأخطاء صعب (توزيع ديناميكي) بسيط (الكود المولد قابل للقراءة)
📌 نقطة رئيسية: اختار نظام Dart البيئي "توليد الكود" بدلاً من "الانعكاس". الحزم الرئيسية مثل json_serializable و freezed و dart_mappable كلها مبنية على توليد الكود. تجميع Flutter's AOT يستبعد الانعكاس بشكل طبيعي.


6. تعيين الحقول مدفوع بالتعليقات التوضيحية

(1) سيناريو بوب: تعيين حقول خط أنابيب البيانات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

: معالج التعليقات التوضيحية اليدوي (يحاكي توليد الكود)

DART
// تعليقات توضيحية مخصصة لتعيين الحقول
class FieldMapping {
  final String csvColumn;
  final String? defaultValue;
  final bool required;

  const FieldMapping({
    required this.csvColumn,
    this.defaultValue,
    this.required = true,
  });
}

class ModelMapping {
  final String tableName;
  const ModelMapping(this.tableName);
}

// نموذج بتعليقات توضيحية لتعيين الحقول
@ModelMapping('customers')
class Customer {
  @FieldMapping(csvColumn: 'customer_id')
  final String id;

  @FieldMapping(csvColumn: 'customer_name', defaultValue: 'Unknown')
  final String name;

  @FieldMapping(csvColumn: 'email', required: false)
  final String? email;

  @FieldMapping(csvColumn: 'total_spent', defaultValue: '0')
  final double totalSpent;

  Customer({
    required this.id,
    required this.name,
    this.email,
    this.totalSpent = 0,
  });

  // تعيين يدوي (في مشروع حقيقي، سيتم توليده)
  static Customer fromCsvMap(Map<String, String> csvRow) {
    return Customer(
      id: csvRow['customer_id'] ?? '',
      name: csvRow['customer_name'] ?? 'Unknown',
      email: csvRow['email'],
      totalSpent: double.tryParse(csvRow['total_spent'] ?? '0') ?? 0,
    );
  }
}

void main() {
  final csvRow = {
    'customer_id': 'CUST-001',
    'customer_name': 'Alice',
    'email': 'alice@example.com',
    'total_spent': '52500.75',
  };

  final customer = Customer.fromCsvMap(csvRow);
  print('Customer: ${customer.id}, ${customer.name}');
  print('Email: ${customer.email ?? "N/A"}');
  print('Total: \$${customer.totalSpent.toStringAsFixed(2)} USD');
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

7. فلسفة التصميم بدون انعكاس

(1) لماذا اختارت Dart عدم الانعكاس

100%
graph TD
  A[الانعكاس مقابل توليد الكود] --> B[الانعكاس]
  A --> C[توليد الكود]
  B --> B1[مرونة وقت التشغيل]
  B --> B2[غير متوافق مع AOT]
  B --> B3[Flutter/Web محظور]
  B --> B4[عبء الأداء]
  C --> C1[يقينية وقت الترجمة]
  C --> C2[متوافق مع AOT]
  C --> C3[ودود مع Flutter/Web]
  C --> C4[تكلفة صفرية في وقت التشغيل]
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.
مبدأ التصميم الوصف
AOT أولاً إصدار Flutter يستخدم تجميع AOT؛ الانعكاس غير متاح
تهز الشجرة المترجم يزيل الكود غير المستخدم؛ الانعكاس يمنع تهز الشجرة
الأداء أولاً الانعكاس له عبء وقت التشغيل؛ توليد الكود بدون عبء
أمان النوع توليد الكود يحافظ على أمان النوع؛ الانعكاس يفقد فحص النوع

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

: توليد الكود كبديل للانعكاس

DART
// بدلاً من تحليل JSON المعتمد على الانعكاس:
// dynamic parseJson(Map<String, dynamic> json, Type type) { ... }  // سيء

// استخدم النهج المعتمد على توليد الكود:
// 1. عرّف النموذج بتعليق توضيحي
// @JsonSerializable()
// class Order { ... }

// 2. شغّل: dart run build_runner build

// 3. الكود المولد يوفر تحليلاً آمن النوع:
// factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);

// الإصدار اليدوي (يحاكي الكود المولد)
class Order {
  final String id;
  final double amount;

  Order({required this.id, required this.amount});

  // fromJson "المولد"
  factory Order.fromJson(Map<String, dynamic> json) => Order(
    id: json['id'] as String,
    amount: (json['amount'] as num).toDouble(),
  );

  // toJson "المولد"
  Map<String, dynamic> toJson() => {
    'id': id,
    'amount': amount,
  };
}

void main() {
  final json = {'id': 'ORD-001', 'amount': 1500.0};
  final order = Order.fromJson(json);  // آمن النوع!
  print(order.toJson());
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

8. المثال الكامل: التعيين مدفوع بالتعليقات التوضيحية لخط أنابيب البيانات

DART
// ============================================
// التعيين مدفوع بالتعليقات التوضيحية لخط أنابيب البيانات
// يحاكي توليد الكود لـ CSV-to-Model
// ============================================

// التعليقات التوضيحية
class CsvField {
  final String column;
  final String? defaultValue;
  final bool required;

  const CsvField({
    required this.column,
    this.defaultValue,
    this.required = true,
  });
}

class CsvModel {
  final String fileName;
  const CsvModel(this.fileName);
}

// فئات النموذج
@CsvModel('orders')
class Order {
  @CsvField(column: 'order_id')
  final String id;

  @CsvField(column: 'total_amount', defaultValue: '0')
  final double amount;

  @CsvField(column: 'order_status', defaultValue: 'pending')
  final String status;

  @CsvField(column: 'category', required: false)
  final String? category;

  Order({
    required this.id,
    required this.amount,
    this.status = 'pending',
    this.category,
  });

  // في مشروع حقيقي، سيتم توليد هذا بواسطة build_runner
  static Order fromCsvRow(Map<String, String> row) => Order(
    id: row['order_id'] ?? '',
    amount: double.tryParse(row['total_amount'] ?? '0') ?? 0,
    status: row['order_status'] ?? 'pending',
    category: row['category'],
  );

  double get tax => amount * 0.08;
  double get total => amount + tax;

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status${category != null ? ", $category" : ""})';
}

// المُعيِّن (يحاكي الكود المولد)
class CsvMapper<T> {
  final T Function(Map<String, String>) fromCsvRow;

  CsvMapper(this.fromCsvRow);

  List<T> mapAll(List<Map<String, String>> rows) =>
      rows.map(fromCsvRow).toList();

  (List<T> valid, List<(int, String)> errors) mapSafe(
      List<Map<String, String>> rows) {
    final valid = <T>[];
    final errors = <(int, String)>[];

    for (var i = 0; i < rows.length; i++) {
      try {
        valid.add(fromCsvRow(rows[i]));
      } catch (e) {
        errors.add((i + 1, e.toString()));
      }
    }
    return (valid, errors);
  }
}

void main() {
  // بيانات CSV محاكاة (مُحَلَّلة بالفعل إلى خرائط)
  final csvRows = <Map<String, String>>[
    {'order_id': 'ORD-001', 'total_amount': '1500.00', 'order_status': 'completed', 'category': 'Electronics'},
    {'order_id': 'ORD-002', 'total_amount': '3200.50', 'order_status': 'completed', 'category': 'Electronics'},
    {'order_id': 'ORD-003', 'total_amount': '890.00', 'order_status': 'pending', 'category': 'Clothing'},
    {'order_id': 'ORD-004', 'total_amount': '50.00', 'order_status': 'completed'},
  ];

  // تعيين إلى كائنات النموذج
  final mapper = CsvMapper<Order>(Order.fromCsvRow);
  final (valid, errors) = mapper.mapSafe(csvRows);

  print('=== DataPipeline CSV Import Report ===');
  print('Rows:   ${csvRows.length}');
  print('Valid:  ${valid.length}');
  print('Errors: ${errors.length}');

  if (errors.isNotEmpty) {
    print('\nErrors:');
    for (final (line, msg) in errors) {
      print('  Line $line: $msg');
    }
  }

  // معالجة الطلبات الصالحة
  double totalRevenue = 0;
  for (final order in valid) {
    if (order.status == 'completed') {
      totalRevenue += order.total;
    }
    print('  $order');
  }

  print('\nRevenue (completed): \$${totalRevenue.toStringAsFixed(2)} USD');
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو باستخدام `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف نتائج التنفيذ قليلاً حسب إصدار SDK.

الإخراج:

TEXT 📖 للعرض فقط
=== DataPipeline CSV Import Report ===
Rows:   4
Valid:  4
Errors: 0
  Order(ORD-001, $1500.00, completed, Electronics)
  Order(ORD-002, $3200.50, completed, Electronics)
  Order(ORD-003, $890.00, pending, Clothing)
  Order(ORD-004, $50.00, completed)

Revenue (completed): $5132.54 USD

❓ أسئلة شائعة

س: لماذا لا يدعم Flutter dart:mirrors؟ ج: يستخدم Flutter تجميع AOT إلى كود أصلي. يتطلب AOT تحديد جميع معلومات النوع في وقت الترجمة. يبحث الانعكاس ديناميكياً عن الأنواع في وقت التشغيل، مما يتعارض مع تهز الشجرة وتحسينات التجميع في AOT.

س: ما فائدة التعليقات التوضيحية في حد ذاتها؟ ج: التعليقات التوضيحية في حد ذاتها لا تنفذ أي منطق؛ هي مجرد بيانات وصفية. تحتاج إلى أن تُقرأ بواسطة الانعكاس (VM) أو تُعالج بواسطة مولد كود (build_runner) لتكون فعالة.

س: هل أحتاج إلى إعادة تشغيل build_runner في كل مرة أغير الكود؟ ج: نعم، لكن هناك وضع --watch يراقب تغييرات الملفات ويعيد التوليد تلقائياً. استخدم وضع المراقبة أثناء التطوير ووضع البناء قبل الإصدار.

س: ما الفرق بين json_serializable وكتابة fromJson يدوياً؟ ج: json_serializable يولد الكود تلقائياً، متجنباً أخطاء الكتابة اليدوية، ويدعم الكائنات المتداخلة والتحويلات المخصصة. الكتابة اليدوية بسيطة ولكنها عرضة للأخطاء، والكائنات المتداخلة تصبح أكثر إيلاماً.

س: هل ستدعم Dart الماكروهات في المستقبل؟ ج: فريق Dart يطور نظام ماكروهات (حزمة macro)، بهدف استبدال بعض وظائف build_runner لتجربة مطور أفضل. ومع ذلك، فهي ليست مستقرة بعد.

س: هل يزيد توليد الكود من حجم الحزمة؟ ج: نعم، لأن الكود المولد مدرج في ناتج التجميع. ومع ذلك، الانعكاس يزيد الحجم أيضاً (بمنع تهز الشجرة)؛ الفرق ضئيل.

س: كيف أصحح الكود المولد؟ ج: يمكنك فتح وقراءة ملفات .g.dart المولدة مباشرة لتصحيح الأخطاء. الكود المولد بواسطة build_runner هو كود Dart قياسي؛ يمكنك تعيين نقاط توقف.


📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): عرّف 3 تعليقات توضيحية مخصصة (@ApiEndpoint, @Required, @DefaultValue) وطبقها على فئة. استخدم dart:mirrors لقراءة معلومات التعليقات التوضيحية (ملاحظة: يمكن التشغيل فقط على VM).
  2. متوسط (صعوبة ⭐⭐): أنشئ مشروع json_serializable، ولد كود fromJson/toJson لفئة Order بـ 5 حقول. اعرض ملف .g.dart المولد وافهم منطق التوليد.
  3. تحدي (صعوبة ⭐⭐⭐): صمم مولد كود بسيط: اقرأ تعريفات الفئات المعلَّقة بـ @CsvField وولد طريقة fromCsvRow ثابتة. تلميح: يمكنك استخدام حزمة source_gen أو قوالب سلسلة نصية بسيطة.

← الدرس السابق | الدرس التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%