Dart: دوال Dart — مواطنين من الدرجة الأولى والإغلاقات

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

الدوال هي لبنات بناء الكود — الدوال الصغيرة تتحد لبناء أنظمة كبيرة.

1. ما ستتعلمه


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

(1) نقطة الألم: دالة 200 سطر، 8 معاملات موضعية، توقيع لا يمكن قراءته

كانت دالة processOrder لدى ليزا: void processOrder(String id, double amount, String currency, String customerId, String address, String city, String country, String zipCode). عند استدعاء: processOrder('123', 99.99, 'USD', 'C001', '123 Main St', 'NYC', 'USA', '10001') — ما هو المعامل الرابع؟ ما هي المدينة؟ كود غير قابل للقراءة. سألت زميلتها: "استخدمي المعلمات المسماة."

(2) حل Dart

DART
// ❌ المعلمات الموضعية — يصعب قراءتها
processOrder('123', 99.99, 'USD', 'C001', '123 Main St', 'NYC', 'USA', '10001');

// ✅ المعلمات المسماة — واضحة
processOrder(
  orderId: '123',
  amount: 99.99,
  currency: 'USD',
  customerId: 'C001',
  street: '123 Main St',
  city: 'NYC',
  country: 'USA',
  zipCode: '10001',
);

(3) النتيجة: 8 معاملات موضعية → 8 معلمات مسماة، قابلية قراءة +5 مرات

المؤشر المعلمات الموضعية المعلمات المسماة
قابلية القراءة منخفضة عالية
عدد الأخطاء الناجمة عن الترتيب الخاطئ 3/شهر 0
وقت مراجعة الكود 5 دقائق/استدعاء 30 ثانية/استدعاء
توثيق ذاتي لا نعم (اسم المعلمة = وثيقة)
TEXT 📖 للعرض فقط
قبل: معاملات موضعية 8، 3 أخطاء ترتيب/شهر
بعد: معلمات مسماة 8، 0 أخطاء
ROI: قابلية قراءة +5 مرات

3. إعلان الدوال الأساسي

(1) 4 طرق للإعلان عن الدوال

DART
// 1. دالة كاملة
int add(int a, int b) {
  return a + b;
}

// 2. دالة سهم (=>) — لجسم دالة واحد
int add2(int a, int b) => a + b;

// 3. دالة بدون قيمة إرجاع
void greet(String name) {
  print('مرحبًا، $name!');
}

// 4. دالة بدون معاملات
String getGreeting() => 'مرحبًا بالعالم!';

(2) أنواع الإرجاع

DART
// نوع الإرجاع الصريح
int sum(List<int> numbers) {
  int total = 0;
  for (final n in numbers) {
    total += n;
  }
  return total;
}

// نوع الإرجاع المستنتج
sum2(List<int> numbers) {  // int مستنتج من return total
  int total = 0;
  for (final n in numbers) {
    total += n;
  }
  return total;
}

// ✅ موصى به: نوع الإرجاع الصريح لواجهات API
// ✅ استنتاج النوع للدوال المحلية القصيرة

(3) دالة السهم (Arrow Functions)

DART
// جسم دالة واحد فقط
int double(int x) => x * 2;

// كتلة if-else واحدة
String describe(int n) => n > 0 ? 'positive' : 'non-positive';

// استدعاء دالة واحدة
List<int> getNumbers() => [1, 2, 3];

// ❌ لا يمكن أن يكون لها عدة أسطور
// int bad(int x) => 
//   int y = x * 2;
//   return y;

// ✅ استخدم كتل الدوال الكاملة
int good(int x) {
  final y = x * 2;
  return y;
}

(4) void كنوع إرجاع

DART
// void — لا تُرجع قيمة
void printMessage(String message) {
  print(message);
}

// void يمكنها أيضًا استدعاء return بدون قيمة
void process(List<int> data) {
  if (data.isEmpty) {
    return;  // خروج مبكر، لا قيمة إرجاع
  }
  for (final item in data) {
    print(item);
  }
}

▶ مثال: مكتبة دوال الرياضيات

DART
class MathUtils {
  // دوال سهم — جملة واحدة
  static int square(int x) => x * x;
  static int cube(int x) => x * x * x;
  static int max(int a, int b) => a > b ? a : b;
  static int min(int a, int b) => a < b ? a : b;
  static bool isEven(int n) => n % 2 == 0;
  static bool isOdd(int n) => n % 2 != 0;
  static int abs(int n) => n < 0 ? -n : n;

  // دوال كاملة — منطقية متعددة الأسطر
  static int factorial(int n) {
    if (n < 0) throw ArgumentError('n must be non-negative');
    if (n == 0 || n == 1) return 1;
    return n * factorial(n - 1);
  }

  static int gcd(int a, int b) {
    while (b != 0) {
      final t = b;
      b = a % b;
      a = t;
    }
    return a;
  }

  static List<int> primes(int n) {
    if (n < 2) return [];
    final sieve = List<bool>.filled(n + 1, true);
    sieve[0] = sieve[1] = false;
    for (int i = 2; i * i <= n; i++) {
      if (sieve[i]) {
        for (int j = i * i; j <= n; j += i) {
          sieve[j] = false;
        }
      }
    }
    return [for (int i = 2; i <= n; i++) if (sieve[i]) i];
  }
}

void main() {
  print(MathUtils.square(5));      // 25
  print(MathUtils.factorial(5));   // 120
  print(MathUtils.primes(20));     // [2, 3, 5, 7, 11, 13, 17, 19]
}
TEXT 📖 للعرض فقط
> **الإخراج:** مكتبة دوال رياضية — دوال سهم للحسابات البسيطة، دوال كاملة للمنطق المعقد.

4. المعلمات الاختيارية: المسماة والموضعية

(1) المعلمات الموضعية الاختيارية []

DART
// المعلمات الاختيارية في []
String greet(String name, [String greeting = 'مرحبًا']) {
  return '$greeting، $name!';
}

print(greet('Bob'));              // 'مرحبًا، Bob!'
print(greet('Bob', 'صباح الخير'));  // 'صباح الخير، Bob!'

// معلمات اختيارية متعددة
String createUser(String name, [int age = 18, String city = 'غير معروف']) {
  return '$name ($age، $city)';
}

print(createUser('Bob'));                // 'Bob (18، غير معروف)'
print(createUser('Bob', 25));            // 'Bob (25، غير معروف)'
print(createUser('Bob', 25, 'NYC'));    // 'Bob (25، NYC)'

// ❌ لا يمكن تخطي معامل موضعي
// print(createUser('Bob', city: 'NYC'));  // خطأ

(2) المعلمات المسماة الاختيارية {}

DART
// المعلمات المسماة الاختيارية في {}
String createUser({
  required String name,
  int age = 18,
  String city = 'غير معروف',
  String? email,
}) {
  return '$name ($age، $city، email: ${email ?? "لا يوجد"})';
}

// المعلمات المطلوبة (required) — يجب تمريرها
print(createUser(name: 'Bob'));  // 'Bob (18، غير معروف، email: لا يوجد)'

// تمرير بالاسم — أي ترتيب
print(createUser(age: 25, name: 'Alice'));  // 'Alice (25، غير معروف، email: لا يوجد)'

// تخطي المعلمات الاختيارية
print(createUser(name: 'Bob', email: 'bob@example.com'));  // 'Bob (18، غير معروف، email: bob@example.com)'

// ❌ لا يمكن تمرير معلمة مسماة موضعيًا
// print(createUser('Bob'));  // خطأ — يجب تمرير name بالاسم

(3) مزج المعلمات الموضعية والمسماة

DART
// المعلمات الموضعية أولاً، ثم الاختيارية المسماة
void registerUser(
  String name,                      // موضعية مطلوبة
  int age,                          // موضعية مطلوبة
  {String? email, String? phone}  // مسماة اختيارية
) {
  print('Name: $name, Age: $age, Email: ${email ?? "لا"}, Phone: ${phone ?? "لا"}');
}

// ✅ تمرير المعلمات الموضعية بالترتيب، المسماة بالاسم
registerUser('Bob', 25, email: 'bob@example.com');
// Name: Bob, Age: 25, Email: bob@example.com, Phone: لا

// ✅ تمرير المعلمات المسماة بأي ترتيب
registerUser('Bob', 25, phone: '555-1234', email: 'bob@example.com');

// ❌ معامل مسماة قبل موضعية
// registerUser(name: 'Bob', 25);  // خطأ

(4) required — جعل المعلمات المسماة إلزامية

DART
// قبل Dart 2.12، كان مطلوبًا تحديد الافتراضي
// بعد Dart 2.12 مع null safety، استخدم required
class User {
  final String name;
  final String email;
  final int age;

  // مع required — يجب تمرير المعلمات
  User({
    required this.name,
    required this.email,
    required this.age,
  });
}

final user = User(name: 'Bob', email: 'bob@example.com', age: 25);
// ❌ user = User();  // خطأ في وقت الترجمة

(5) مزج المعلمات الموضعية الاختيارية والمسماة الاختيارية

DART
// موضعية اختيارية + مسماة اختيارية
String formatName(
  String firstName, [
  String? middleName,
  String? lastName,
]) {
  // ❌ خطأ — middleName و lastName ليسا مسماة
  // return '$firstName $middleName $lastName';
  return '$firstName ${middleName ?? ''} ${lastName ?? ''}'.trim();
}

// ✅ الإصلاح: مسماة
String formatNameV2(
  String firstName, {
  String? middleName,
  String? lastName,
}) {
  return '$firstName ${middleName ?? ''} ${lastName ?? ''}'.trim();
}

print(formatNameV2('Bob', lastName: 'Smith'));  // 'Bob Smith'

(6) أفضل الممارسات

DART
// ✅ جميع المعلمات المسماة (الأفضل للقراءة)
void createUser({
  required String name,
  required String email,
  int age = 18,
  String? phone,
}) {
  // ...
}

// ❌ الكثير من المعلمات الموضعية (يصعب قراءتها)
void createUser(String name, String email, int age, String phone) {
  // ...
}

// ❌ مزيج فوضوي
void createUser(String name, String email, {int age, String? phone, String? address, String? city}) {
  // ...
}

▶ مثال: فئة طلب مع معاملات مسماة

DART
class Order {
  final String orderId;
  final String customerId;
  final List<String> items;
  final double totalAmount;
  final String currency;
  final String? shippingAddress;
  final String? billingAddress;
  final String? couponCode;
  final bool giftWrap;
  final String? notes;

  Order({
    required this.orderId,
    required this.customerId,
    required this.items,
    required this.totalAmount,
    this.currency = 'USD',
    this.shippingAddress,
    this.billingAddress,
    this.couponCode,
    this.giftWrap = false,
    this.notes,
  });

  @override
  String toString() => 'Order($orderId, $totalAmount $currency, ${items.length} items)';
}

void main() {
  // ✅ قراءة عالية
  final order1 = Order(
    orderId: 'ORD-001',
    customerId: 'CUST-001',
    items: ['Laptop', 'Mouse'],
    totalAmount: 1299.99,
    currency: 'USD',
    giftWrap: true,
    notes: 'الرجاء التعامل بحذر',
  );

  // ❌ لا توجد طريقة لقراءة هذا
  // final order2 = Order('ORD-002', 'CUST-002', ['Phone'], 999.99, 'USD', null, null, null, true, null);
}
TEXT 📖 للعرض فقط
> **الإخراج:** Order مع 10 معلمات مسماة — 10 أسطر قابلة للقراءة بدلاً من سطر واحد غير قابل للقراءة.

5. الإغلاقات (Closures)

(1) ما هو الإغلاق؟

الإغلاق = دالة + البيئة المعجمية الخاصة بها. في Dart، الدوال هي مواطنين من الدرجة الأولى، يمكنها التقاط المتغيرات من النطاق المحيط.

DART
// دالة عادية — لا تلتقط متغيرات
int add(int a, int b) => a + b;

// إغلاق — يلتقط متغير من النطاق الخارجي
Function makeCounter() {
  int count = 0;  // متغير محلي
  return () => ++count;  // إغلاق — يلتقط count
}

final counter = makeCounter();
print(counter());  // 1
print(counter());  // 2
print(counter());  // 3
// count لا يمكن الوصول إليه من الخارج، فقط من خلال الإغلاق

(2) الإغلاقات المستقلة

DART
// كل استدعاء لـ makeCounter ينشئ count جديد
final counter1 = makeCounter();
final counter2 = makeCounter();

print(counter1());  // 1
print(counter1());  // 2
print(counter2());  // 1 (count جديد)
print(counter2());  // 2
print(counter1());  // 3

(3) التقاط المتغيرات

DART
// التقاط متغير محلي
Function makeGreeter(String greeting) {
  return (String name) => '$greeting، $name!';
}

final hello = makeGreeter('مرحبًا');
final hi = makeGreeter('مرحبا');

print(hello('Bob'));  // 'مرحبًا، Bob!'
print(hi('Alice'));   // 'مرحبا، Alice!'

// التقاط متغير في حلقة
List<Function> makeFunctions() {
  final functions = <Function>[];
  for (int i = 0; i < 3; i++) {
    functions.add(() => i);
  }
  return functions;
}

final funcs = makeFunctions();
print(funcs[0]());  // 3 (وليس 0! لأن i = 3 بعد الحلقة)
print(funcs[1]());  // 3
print(funcs[2]());  // 3

(4) تجنب الفخ الشائع — التقاط المتغير

DART
// ❌ الفخ الكلاسيكي — جميع الإغلاقات تشير إلى نفس المتغير
List<Function> makeBadFunctions() {
  final functions = <Function>[];
  for (int i = 0; i < 3; i++) {
    functions.add(() => i);
  }
  return functions;
}

final badFuncs = makeBadFunctions();
print(badBadFunctions[0]());  // 3 — وليس 0!
print(badBadFunctions[1]());  // 3
print(badBadFunctions[2]());  // 3

// ✅ الحل 1: استخدام متغير محلي جديد
List<Function> makeGoodFunctions1() {
  final functions = <Function>[];
  for (int i = 0; i < 3; i++) {
    final j = i;  // متغير محلي جديد لكل تكرار
    functions.add(() => j);
  }
  return functions;
}

// ✅ الحل 2: استخدام mapIndexed
List<Function> makeGoodFunctions2() {
  return [for (int i = 0; i < 3; i++) () => i];
}

// ✅ الحل 3: استخدام list.generate مع index
List<Function> makeGoodFunctions3() {
  return List.generate(3, (i) => () => i);
}

final good1 = makeGoodFunctions1();
print(good1[0]());  // 0
print(good1[1]());  // 1
print(good1[2]());  // 2

(5) الإغلاقات كقيم إرجاع

DART
// دالة تُرجع دالة
Function adder(int amount) {
  return (int x) => x + amount;
}

final add5 = adder(5);
final add10 = adder(10);

print(add5(3));   // 8
print(add10(3));  // 13

(6) الإغلاقات في callbacks

DART
// مثال: معالجة القائمة باستخدام forEach
List<int> numbers = [1, 2, 3, 4, 5];
int sum = 0;
numbers.forEach((n) => sum += n);
print('sum: $sum');  // 15

// map
List<int> doubled = numbers.map((n) => n * 2).toList();
print('doubled: $doubled');  // [2, 4, 6, 8, 10]

// where
List<int> evens = numbers.where((n) => n.isEven).toList();
print('evens: $evens');  // [2, 4]

▶ مثال: نظام تسجيل أحداث مع إغلاقات

DART
// نظام اشتراك في الأحداث
class EventEmitter {
  final Map<String, List<Function>> _listeners = {};

  void on(String event, Function listener) {
    _listeners.putIfAbsent(event, () => []).add(listener);
  }

  void emit(String event, [dynamic data]) {
    final listeners = _listeners[event] ?? [];
    for (final listener in listeners) {
      listener(data);
    }
  }

  void off(String event, Function listener) {
    _listeners[event]?.remove(listener);
  }
}

void main() {
  final emitter = EventEmitter();

  // ✅ الإغلاق يلتقط المتغيرات
  var clickCount = 0;
  emitter.on('click', (data) {
    clickCount++;
    print('Click #$clickCount: $data');
  });

  emitter.emit('click', 'Button1');
  // Click #1: Button1

  emitter.emit('click', 'Button2');
  // Click #2: Button2

  // ✅ إغلاق مع معاملات
  emitter.on('error', (error) {
    print('Error occurred: $error');
  });

  emitter.emit('error', 'Network timeout');
  // Error occurred: Network timeout
}
TEXT 📖 للعرض فقط
> **الإخراج:** EventEmitter يستخدم الإغلاقات كـ event listeners — كل إغلاق يلتقط متغيراته الخاصة.

6. الدوال ذات الرتبة العليا (Higher-Order Functions)

(1) ما هي الدالة ذات الرتبة العليا؟

دالة تأخذ دالة أخرى كمعامل أو تُرجع دالة.

DART
// map — تأخذ دالة كمعامل
List<int> numbers = [1, 2, 3, 4, 5];
List<int> doubled = numbers.map((n) => n * 2).toList();
print(doubled);  // [2, 4, 6, 8, 10]

// where — تأخذ دالة كمعامل
List<int> evens = numbers.where((n) => n.isEven).toList();
print(evens);  // [2, 4]

// reduce — تأخذ دالة كمعامل
int sum = numbers.reduce((a, b) => a + b);
print(sum);  // 15

(2) دوال مخصصة ذات رتبة عليا

DART
// forEach مخصص
void myForEach<T>(List<T> list, void Function(T) action) {
  for (final item in list) {
    action(item);
  }
}

myForEach([1, 2, 3], (n) => print('Number: $n'));
// Number: 1
// Number: 2
// Number: 3

// map مخصص
List<R> myMap<T, R>(List<T> list, R Function(T) transform) {
  final result = <R>[];
  for (final item in list) {
    result.add(transform(item));
  }
  return result;
}

List<int> squares = myMap([1, 2, 3], (n) => n * n);
print(squares);  // [1, 4, 9]

(3) Function types

DART
// typedef — اسم لنوع الدالة
typedef IntOperation = int Function(int, int);

int add(IntOperation op, int a, int b) => op(a, b);

int multiply(int a, int b) => a * b;
int divide(int a, int b) => a ~/ b;

print(add(multiply, 3, 4));  // 12
print(add(divide, 10, 3));   // 3

(4) Closure factory

DART
// إرجاع دالة مخصصة
Function makeMultiplier(int factor) {
  return (int x) => x * factor;
}

final triple = makeMultiplier(3);
final quadruple = makeMultiplier(4);

print(triple(5));       // 15
print(quadruple(5));   // 20

(5) تمرير الدوال كقيم

DART
// callback نمط
typedef Callback = void Function(int);

void processData(List<int> data, Callback callback) {
  for (var i = 0; i < data.length; i++) {
    callback(data[i]);
  }
}

void processItem(int item) {
  print('Processing: $item');
}

processData([1, 2, 3], processItem);
// Processing: 1
// Processing: 2
// Processing: 3

// مع lambda
processData([4, 5, 6], (item) => print('Item: $item'));

▶ مثال: نظام التصفية والفرز

DART
// دوال ذات رتبة عليا قابلة لإعادة الاستخدام
class ListUtils {
  // filter
  static List<T> filter<T>(List<T> list, bool Function(T) predicate) {
    return list.where(predicate).toList();
  }

  // map
  static List<R> map<T, R>(List<T> list, R Function(T) transform) {
    return list.map(transform).toList();
  }

  // reduce
  static T reduce<T>(List<T> list, T Function(T, T) combine) {
    return list.reduce(combine);
  }

  // find
  static T? find<T>(List<T> list, bool Function(T) predicate) {
    for (final item in list) {
      if (predicate(item)) return item;
    }
    return null;
  }

  // groupBy
  static Map<K, List<T>> groupBy<T, K>(List<T> list, K Function(T) keyFn) {
    final result = <K, List<T>>{};
    for (final item in list) {
      result.putIfAbsent(keyFn(item), () => []).add(item);
    }
    return result;
  }
}

void main() {
  final users = [
    {'name': 'Alice', 'age': 30, 'city': 'NYC'},
    {'name': 'Bob', 'age': 25, 'city': 'LA'},
    {'name': 'Charlie', 'age': 35, 'city': 'NYC'},
    {'name': 'David', 'age': 28, 'city': 'SF'},
  ];

  // تصفية — أكبر من 28
  final senior = ListUtils.filter(users, (u) => u['age']! > 28);
  print('Senior: $senior');

  // تحويل — استخراج الأسماء
  final names = ListUtils.map(users, (u) => u['name']!);
  print('Names: $names');

  // التجميع — حساب متوسط العمر
  final totalAge = ListUtils.reduce(users, (a, b) => a + b['age']!);
  print('Total age: $totalAge');
  // print('Avg age: ${totalAge / users.length}');  // 29.5

  // find
  final alice = ListUtils.find(users, (u) => u['name'] == 'Alice');
  print('Alice: $alice');

  // groupBy
  final byCity = ListUtils.groupBy(users, (u) => u['city']!);
  print('By city: $byCity');
  // {NYC: [Alice, Charlie], LA: [Bob], SF: [David]}
}
TEXT 📖 للعرض فقط
> **الإخراج:** ListUtils مع 5 دوال ذات رتبة عليا — filter, map, reduce, find, groupBy.

7. المعلمات الاختيارية الموضعية والمسماة معًا

(1) مزيج كامل

DART
// موضعية مطلوبة + موضعية اختيارية + مسماة مطلوبة + مسماة اختيارية
void complexFunction(
  String a,              // موضعية مطلوبة
  int b,                 // موضعية مطلوبة
  [String? c,           // موضعية اختيارية
  double? d,             // موضعية اختيارية
   ]
  {required String e,    // مسماة مطلوبة
  bool? f,              // مسماة اختيارية
  List<String>? g,      // مسماة اختيارية
  }
) {
  print('a=$a, b=$b, c=$c, d=$d, e=$e, f=$f, g=$g');
}

// استدعاء صحيح — المعلمات الموضعية بالترتيب، المسماة بالاسم
complexFunction('a', 1, 'c', 2.0, e: 'E', f: true, g: ['x', 'y']);
// a=a, b=1, c=c, d=2.0, e=E, f=true, g=[x, y]

// ❌ تمرير معلمة مسماة قبل الموضعية
// complexFunction(e: 'E', 'a', 1);  // خطأ

(2) القواعد

DART
// القواعد:
// 1. المعلمات الموضعية في البداية
// 2. المعلمات الموضعية الاختيارية في []
// 3. المعلمات المسماة في النهاية في {}
// 4. لا يمكن خلط موضعية ومسماة في نفس الموضع

// ✅ صحيح
void good1(String a, [int? b], {String? c}) {}

// ❌ خطأ — مسماة قبل موضعية اختيارية
// void bad1(String a, {String? c}, [int? b]) {}

// ❌ خطأ — لا يمكن وضع المعلمات الاختيارية قبل الإلزامية
// void bad2([int? a], String b) {}

(3) ترتيب المعلمات

DART
// الترتيب الموصى به:
// 1. المعلمات الموضعية الإلزامية (مطلوبة دائمًا)
// 2. المعلمات الموضعية الاختيارية
// 3. المعلمات المسماة الإلزامية (required)
// 4. المعلمات المسماة الاختيارية

void recommendedOrder(
  String orderId,        // 1. موضعية إلزامية
  String customerId,     // 1. موضعية إلزامية
  [String? notes,        // 2. موضعية اختيارية
   double? discount,    // 2. موضعية اختيارية
   ],
  {required DateTime orderDate,  // 3. مسماة إلزامية
  String? shippingAddress,       // 4. مسماة اختيارية
  String? paymentMethod,        // 4. مسماة اختيارية
  }
) {
  print('Order $orderId for $customerId');
}

(4) المعلمات الاختيارية في Dart vs JavaScript

DART
// Dart — جميع المعلمات الاختيارية في {} أو []
// المعلمات المسماة {} يمكنها استخدام required
void fn({required int a, int b = 0}) {}

// JavaScript — جميع المعلمات الاختيارية = undefined
// function fn({a, b = 0}) {}

// الفرق: Dart أكثر صرامة، يجب التصريح بـ required
// JavaScript أكثر مرونة، يمكن حذف أي معامل

▶ مثال: فئة HTTP Client

DART
class HttpRequest {
  final String url;
  final String method;
  final Map<String, String>? headers;
  final Map<String, dynamic>? queryParams;
  final String? body;
  final int timeout;
  final bool followRedirects;
  final String? userAgent;
  final List<int>? acceptedStatusCodes;

  HttpRequest({
    required this.url,
    this.method = 'GET',
    this.headers,
    this.queryParams,
    this.body,
    this.timeout = 30,
    this.followRedirects = true,
    this.userAgent,
    this.acceptedStatusCodes,
  });

  @override
  String toString() => '$method $url (timeout=${timeout}s)';
}

void main() {
  // ✅ استدعاء بسيط
  final req1 = HttpRequest(url: 'https://api.example.com/users');
  print(req1);

  // ✅ استدعاء كامل
  final req2 = HttpRequest(
    url: 'https://api.example.com/users',
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: '{"name": "Bob"}',
    timeout: 60,
    followRedirects: false,
  );
  print(req2);
}
TEXT 📖 للعرض فقط
> **الإخراج:** HttpRequest مع 8 معلمات مسماة — استدعاء بسيط بـ 1 معامل، استدعاء كامل بـ 5 معاملات.

8. المعلمات الاختيارية المتقدمة

(1) تمرير المعلمات الاختيارية كمجموعة

DART
typedef UserData = Map<String, dynamic>;

void createUser(UserData data) {
  print('User: ${data['name']}, age: ${data['age']}');
}

createUser({'name': 'Bob', 'age': 25});
createUser({'name': 'Alice'});

(2) المعلمات الاختيارية الديناميكية

DART
// معلمات اختيارية لا نهائية باستخدام Spread
void logAll(String prefix, [List<String>? tags = null]) {
  print('$prefix: ${tags?.join(', ') ?? "بدون علامات"}');
}

logAll('بداية');
logAll('تحذير', ['security', 'urgent']);

(3) المعلمات كـ Map

DART
// خريطة المعاملات
Map<String, dynamic> config = {
  'timeout': 60,
  'retries': 3,
  'debug': true,
};

void configure({required Map<String, dynamic> options}) {
  print('Configured: $options');
}

configure(options: config);

(4) وسيط مسمى forwarding

DART
class Service {
  final String name;
  final int timeout;
  final bool enabled;

  Service({required this.name, this.timeout = 30, this.enabled = true});

  // تمرير المعلمات إلى مُنشئ الفئة الفرعية
  Service.subClass({required super.name, super.timeout, super.enabled})
      : super();

  @override
  String toString() => 'Service($name, timeout=$timeout, enabled=$enabled)';
}

(5) أسلوب API الحديث

DART
// ✅ API الحديث — كل المعلمات مسماة
class ApiClient {
  final String baseUrl;
  final String? apiKey;
  final Duration timeout;
  final int maxRetries;
  final Map<String, String> defaultHeaders;

  ApiClient({
    required this.baseUrl,
    this.apiKey,
    this.timeout = const Duration(seconds: 30),
    this.maxRetries = 3,
    this.defaultHeaders = const {},
  });
}

// ❌ API القديم — المعلمات الموضعية
// class OldApiClient(String baseUrl, [String? apiKey, Duration? timeout]) {}

9. نصائح عملية

(1) متى تستخدم المعلمات الموضعية مقابل المسماة

DART
// ✅ موضعية — 1-2 معامل، المعنى واضح من السياق
int add(int a, int b) => a + b;
String repeat(String s, int times) => s * times;

// ✅ مسماة — 3+ معامل، المعنى قد يكون غامضًا
void process({
  required String input,
  String? output,
  bool verbose = false,
  int retries = 3,
}) {}

(2) جعل الكود أكثر قابلية للقراءة

DART
// ✅ استخراج المعلمات المعقدة إلى متغيرات
void process() {
  final verbose = true;
  final retries = 3;
  final outputPath = '/tmp/output.txt';
  doProcess(verbose: verbose, retries: retries, outputPath: outputPath);
}

// ❌ تمرير القيم الحرفية
doProcess(verbose: true, retries: 3, outputPath: '/tmp/output.txt');

(3) تذكر القيم الافتراضية المعقولة

DART
class Config {
  // ✅ قيم افتراضية معقولة
  int port = 8080;
  String host = 'localhost';
  Duration timeout = const Duration(seconds: 30);
  int maxConnections = 100;
}

(4) استخدام تعريف نوع لتبسيط الأنواع المعقدة

DART
// قبل typedef
List<int> filter(List<int> list, bool Function(int) predicate) {
  return list.where(predicate).toList();
}

// بعد typedef
typedef Predicate<T> = bool Function(T);

List<int> filter(List<int> list, Predicate<int> predicate) {
  return list.where(predicate).toList();
}

(5) تسمية المعلمات بشكل واضح

DART
// ✅ أسماء وصفية
void registerUser({
  required String email,
  required String password,
  String? displayName,
  bool sendWelcomeEmail = true,
}) {}

// ❌ أسماء مختصرّة
void registerUser({
  required String e,
  required String p,
  String? n,
  bool s = true,
}) {}

▶ مثال: مكتبة دوال شاملة

DART
typedef Predicate<T> = bool Function(T);
typedef Transform<T, R> = R Function(T);
typedef Reducer<T> = T Function(T, T);
typedef Action<T> = void Function(T);

class FunctionalUtils {
  // filter
  static List<T> filter<T>(List<T> list, Predicate<T> predicate) {
    return list.where(predicate).toList();
  }

  // map
  static List<R> map<T, R>(List<T> list, Transform<T, R> transform) {
    return list.map(transform).toList();
  }

  // reduce
  static T reduce<T>(List<T> list, Reducer<T> reducer) {
    return list.reduce(reducer);
  }

  // forEach
  static void forEach<T>(List<T> list, Action<T> action) {
    list.forEach(action);
  }

  // find
  static T? find<T>(List<T> list, Predicate<T> predicate) {
    for (final item in list) {
      if (predicate(item)) return item;
    }
    return null;
  }

  // any
  static bool any<T>(List<T> list, Predicate<T> predicate) {
    return list.any(predicate);
  }

  // all
  static bool all<T>(List<T> list, Predicate<T> predicate) {
    return list.every(predicate);
  }
}

void main() {
  final numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  // filter + map + reduce
  final sumOfSquares = FunctionalUtils.reduce(
    FunctionalUtils.map(
      FunctionalUtils.filter(numbers, (n) => n.isEven),
      (n) => n * n,
    ),
    (a, b) => a + b,
  );
  print('Sum of squares of evens: $sumOfSquares');
  // Sum of squares of evens: 220 (4+16+36+64+100)

  // forEach مع lambda
  FunctionalUtils.forEach(numbers, (n) {
    if (n > 5) print('Big number: $n');
  });
  // Big number: 6
  // Big number: 7
  // Big number: 8
  // Big number: 9
  // Big number: 10
}
TEXT 📖 للعرض فقط
> **الإخراج:** FunctionalUtils مع 7 دوال ذات رتبة عليا — filter, map, reduce, forEach, find, any, all.

10. ملخص نقاط المعرفة الأساسية لهذا الدرس

# المفهوم المستوى الأهمية
1 4 طرق للإعلان: كاملة، سهم، void، بدون معاملات مبتدئ ⭐⭐⭐⭐⭐
2 المعلمات المسماة {} و required مبتدئ ⭐⭐⭐⭐⭐
3 المعلمات الموضعية الاختيارية [] مبتدئ ⭐⭐⭐⭐
4 الإغلاقات والتقاط المتغيرات متوسط ⭐⭐⭐⭐⭐
5 الدوال ذات الرتبة العليا متوسط ⭐⭐⭐⭐⭐
6 تعريف نوع لنوع الدالة متوسط ⭐⭐⭐
7 Closure factory متوسط ⭐⭐⭐⭐
8 Closure pitfalls في الحلقات متوسط ⭐⭐⭐⭐

❓ أسئلة شائعة

س متى تستخدم صيغة السهم => وكتلة الدالة الكاملة { ... }؟
ج صيغة السهم: عندما يكون جسم الدالة سطر واحد يُرجع قيمة. كتلة الدالة: عندما تحتاج إلى عدة أسطور، أو تأثيرات جانبية، أو منطق معقد. قاعدة: إذا كانت الدالة تحتاج إلى return، فكّر في استخدام =>؛ إذا كانت تحتاج إلى if أو for أو print، استخدم كتلة.
س ما الفرق بين typedef ونوع الدالة المضمنة؟
ج تعريف نوع: اسم مستعار لنوع الدالة، يمكن إعادة استخدامه. مثال: typedef Op = int Function(int, int). نوع الدالة المضمن: int Function(int, int). تعريف نوع يحسن قابلية القراءة ويقلل التكرار.
س لماذا الإغلاقات تلتقط المتغيرات حسب المرجع؟
ج هذا تصميم Dart. الإغلاق يحتفظ بمرجع للمتغير الخارجي، وليس قيمته. لذلك في حلقة، جميع الإغلاقات تشير إلى نفس المتغير i، والذي يكون قيمته النهائية 3. الحل: استخدام متغير محلي جديد داخل الحلقة.
س هل الإغلاقات آمنة من الذاكرة؟
ج نعم ولا. الإغلاقات تلتقط المتغيرات، مما يمنع جمع القمامة من تحرير الذاكرة. إذا أنشأت 10000 إغلاق في حلقة، فسيتم الاحتفاظ بجميع المتغيرات الملتقطة في الذاكرة. هذا تسرب ذاكرة محتمل. في الواقع: كن حذرًا مع الإغلاقات في الحلقات.
س ما الفرق بين void Function(int) و void Function(int)??
ج void Function(int): نوع دالة يأخذ int ويُرجع void. void Function(int)?: نفس النوع، ولكن يمكن أن يكون فارغ. في Dart 3، كل شيء يجب أن يكون nullable صراحةً بـ ?. إذا كانت دالة اختيارية، فاستخدم ?.
س لماذا لا يدعم Dart دوال مسماة فقط (مثل Python *args)?
ج يدعم Dart ذلك! استخدم Map<String, dynamic> أو فئات المعامل. أو استخدم المعلمات الاختيارية المسماة {...}. لا حاجة لـ *args المعقد.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): أنشئ مشروع math_toolkit، واكتب 8 دوال رياضية (square, cube, power, sqrt, abs, max, min, clamp) كدوال سهم (=>). اختبر كل دالة مع 5 مدخلات. اكتب bin/main.dart يقرأ مدخلات المستخدم من سطر الأوامر. تلميح: استخدم num كنوع معامل، و int.parse() لتحويل مدخلات المستخدم.
  2. متوسط (الصعوبة ⭐⭐): أنشئ مشروع config_builder يحتوي على فئة ServerConfig مع 10 معلمات مسماة (host, منفذ, protocol, مهلة, maxConnections, enableLogging, logLevel, sslEnabled, sslCert, sslKey). أنشئ ConfigBuilder بنمط Builder مع 10 طرق set* تُرجع this، ودعم تسلسلي ... اكتب 3 تكوينات مختلفة (development, تجهيز, إنتاج) في bin/main.dart. تلميح: استخدم late final و ? لتأخير التهيئة.
  3. تحدٍّ (الصعوبة ⭐⭐⭐): أنشئ مشروع closure_practice يستكشف 4 سيناريوهات للإغلاقات: ① counter مع زيادة (state preservation) ② دالة factory (makeAdder, makeMultiplier) ③ استدعاء رجعي في حلقة حدث (5 أزرار، كل زر له إغلاق خاص) ④ دالة memoization (دالة تخزن نتائج الدوال الباهظة). اختبر السيناريوهات: ① اتصل بالـ counter 10 مرات، يجب أن يكون 10 ② اتصل بـ makeAdder(5)(3) يجب أن يكون 8 ③ أنشئ 5 أزرار، انقر فوق كل واحد، يجب أن يطبع "Button X clicked" ④ استدعاء expensiveFunc(5) مرتين، المرة الثانية يجب أن تستخدم النتيجة المخزنة. تلميح: استخدم Map<K, V> لتخزين نتائج memoization.

← السابق: التحكم في التدفق في Dart | التالي: الفئات والكائنات →

Web-Tutorial.com

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

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

100%