Dart: الأنواع العامة في Dart

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

الأنواع العامة هي قوالب للكود — اكتب مرة واحدة، أعد الاستخدام لأنواع متعددة، والمترجم يضمن لك أمان النوع.

1. ما ستتعلمه


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

(1) نقطة الألم: كابوس الصيانة من الكود المكرر

كتب تشارلي محللات منفصلة لكل نوع بيانات في خط أنابيب البيانات: OrderParser و ProductParser و CustomerParser. كان المنطق في المحللات الثلاثة متطابقاً تقريباً (قراءة ← تحقق ← تحويل)، مع اختلاف فقط في أنواع الإدخال/الإخراج. في كل مرة احتاج فيها منطق التحليل إلى تعديل، كان يجب تغييره في 3 أماكن. في مرة، أدى تغيير فائت إلى عدم تطبيق قواعد التحقق الجديدة في محلل المنتجات.

(2) الحل العام

وحّد المحللات الثلاثة باستخدام فئة عامة DataTransformer<T, R>. معاملات النوع تتيح للمترجم ضمان أمان النوع، والكود يحتاج إلى الصيانة في مكان واحد فقط.

DART
abstract class DataTransformer<T, R> {
  R transform(T input);
  List<R> batch(List<T> inputs) => inputs.map(transform).toList();
}
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
// بدون الأنواع العامة - List<dynamic> يفقد أمان النوع
void main() {
  List amounts = [1500, 3200, 890];  // List<dynamic>
  amounts.add('not a number');        // لا خطأ ترجمة!
  for (final a in amounts) {
    print((a as int) * 2);  // تحطم وقت التشغيل على 'not a number'
  }
}

// مع الأنواع العامة - أمان النوع وقت الترجمة
void main() {
  List<int> amounts = [1500, 3200, 890];
  // amounts.add('not a number');  // خطأ ترجمة!
  for (final a in amounts) {
    print(a * 2);  // آمن - النوع معروف
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

4. الفئات العامة

(1) تعريف الفئة العامة

▶ مثال

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

: فئة حاوية عامة

DART
class Result<T> {
  final T? data;
  final String? error;
  final bool isSuccess;

  Result.success(this.data)
      : error = null,
        isSuccess = true;

  Result.failure(this.error)
      : data = null,
        isSuccess = false;

  // طريقة تُرجع النوع العام
  T get dataOrThrow {
    if (isSuccess && data != null) return data;
    throw Exception(error ?? 'Unknown error');
  }

  // تحويل قيمة النجاح
  Result<R> map<R>(R Function(T) fn) {
    if (isSuccess && data != null) {
      return Result.success(fn(data));
    }
    return Result.failure(error);
  }
}

void main() {
  final success = Result.success(1500.0);
  final failure = Result<double>.failure('Network timeout');

  print(success.data);           // 1500.0
  print(failure.error);          // Network timeout

  // Map تحول قيمة النجاح
  final formatted = success.map((v) => '\$${v.toStringAsFixed(2)} USD');
  print(formatted.data);         // $1500.00 USD
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

▶ مثال

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

: فئة ذاكرة مخبأة عامة

DART
class Cache<T> {
  final Map<String, T> _store = {};
  final Duration _ttl;

  Cache({Duration ttl = const Duration(minutes: 5)}) : _ttl = ttl;

  void put(String key, T value) => _store[key] = value;

  T? get(String key) => _store[key];

  bool contains(String key) => _store.containsKey(key);

  void remove(String key) => _store.remove(key);

  void clear() => _store.clear();
}

void main() {
  final orderCache = Cache<Map<String, dynamic>>();
  orderCache.put('ORD-001', {'amount': 1500.0, 'status': 'completed'});

  print(orderCache.get('ORD-001'));  // {amount: 1500.0, status: completed}
  print(orderCache.contains('ORD-002'));  // false
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

5. الدوال العامة

▶ مثال

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

: تعريف الدالة العامة

DART
// دالة عامة بمعامل نوع واحد
T firstOrNull<T>(List<T> items) => items.isEmpty ? throw StateError('Empty list') : items.first;

// دالة عامة بمعاملَي نوع
R transform<T, R>(T input, R Function(T) converter) => converter(input);

// دالة عامة مع قيد
double sumNumbers<T extends num>(List<T> items) =>
    items.fold(0.0, (sum, item) => sum + item.toDouble());

void main() {
  print(firstOrNull(['ORD-001', 'ORD-002']));  // ORD-001
  print(firstOrNull([1500, 3200]));             // 1500

  final formatted = transform(1500.0, (v) => '\$${v} USD');
  print(formatted);  // $1500.0 USD

  print(sumNumbers([1, 2, 3, 4, 5]));          // 15.0
  print(sumNumbers([1.5, 2.5, 3.0]));          // 7.0
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

▶ مثال

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

: دوال مساعدة عامة

DART
// أداة تحويل آمن
T? tryCast<T>(dynamic value) {
  if (value is T) return value;
  return null;
}

// تقسيم قائمة إلى مجموعتين
(List<T>, List<T>) partition<T>(List<T> items, bool Function(T) predicate) {
  final matching = <T>[];
  final notMatching = <T>[];
  for (final item in items) {
    (predicate(item) ? matching : notMatching).add(item);
  }
  return (matching, notMatching);
}

void main() {
  // تحويل آمن
  final intVal = tryCast<int>('hello');   // null
  final strVal = tryCast<String>('hello'); // hello

  // تقسيم
  final (high, low) = partition([1500.0, 50.0, 3200.0, 890.0], (v) => v >= 1000);
  print('High: $high');  // [1500.0, 3200.0]
  print('Low: $low');    // [50.0, 890.0]
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

6. القيود العامة

(1) قيد extends واحد

▶ مثال

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

: قيد extends

DART
// يقبل فقط الأنواع التي تمدد num
double average<T extends num>(List<T> values) {
  if (values.isEmpty) return 0;
  final sum = values.fold<num>(0, (a, b) => a + b);
  return sum / values.length;
}

// يقبل فقط الأنواع التي تنفذ Comparable
T findMax<T extends Comparable>(List<T> items) {
  if (items.isEmpty) throw StateError('Empty list');
  return items.reduce((a, b) => a.compareTo(b) >= 0 ? a : b);
}

void main() {
  print(average([10, 20, 30]));          // 20.0
  print(average([1.5, 2.5, 3.0]));       // 2.333...

  print(findMax(['banana', 'apple', 'cherry']));  // cherry
  print(findMax([10, 5, 8]));                     // 10

  // average(['a', 'b']);  // خطأ ترجمة! String لا يمدد num
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

(2) قيود متعددة

▶ مثال

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

: قيود متعددة

DART
// قيود متعددة باستخدام التقاطع
class SortedCollection<T extends Comparable<T>> {
  final List<T> _items = [];

  void add(T item) {
    final index = _items.indexWhere((e) => item.compareTo(e) <= 0);
    if (index == -1) {
      _items.add(item);
    } else {
      _items.insert(index, item);
    }
  }

  List<T> get items => List.unmodifiable(_items);
}

void main() {
  final sorted = SortedCollection<String>();
  sorted.add('cherry');
  sorted.add('apple');
  sorted.add('banana');
  print(sorted.items);  // [apple, banana, cherry]
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

7. التعاون بين الأنواع العامة والمجموعات

▶ مثال

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

: عمليات مجموعة آمنة النوع

DART
// مخزن مفتاح-قيمة آمن النوع
class KeyValueStore<K, V> {
  final Map<K, V> _data = {};

  void put(K key, V value) => _data[key] = value;

  V? get(K key) => _data[key];

  // تحويل جميع القيم
  Map<K, R> mapValues<R>(R Function(V) converter) {
    return _data.map((key, value) => MapEntry(key, converter(value)));
  }

  // تصفية حسب نوع المفتاح
  Map<K, V> whereKey(bool Function(K) predicate) {
    return Map.fromEntries(
      _data.entries.where((e) => predicate(e.key)),
    );
  }
}

void main() {
  final store = KeyValueStore<String, double>();
  store.put('Electronics', 3600.0);
  store.put('Books', 170.0);
  store.put('Clothing', 890.0);

  // تحويل القيم إلى سلاسل منسقة
  final formatted = store.mapValues((v) => '\$${v.toStringAsFixed(2)} USD');
  print(formatted);  // {Electronics: $3600.00 USD, Books: $170.00 USD, Clothing: $890.00 USD}
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

8. سيناريو بوب: مُحوِّل البيانات العام

100%
classDiagram
  class DataTransformer~T, R~ {
    +transform(T input) R
    +batch(List~T~ inputs) List~R~
    +validate(T input) bool
  }
  class OrderParser {
    +transform(String) Order
  }
  DataTransformer <|-- OrderParser
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

▶ مثال

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

: إطار مُحوِّل البيانات العام

DART
// واجهة المُحوِّل الأساسية
abstract class DataTransformer<T, R> {
  R transform(T input);
  String get name;

  List<R> batch(List<T> inputs) {
    final results = <R>[];
    final errors = <(int, String)>[];

    for (var i = 0; i < inputs.length; i++) {
      try {
        if (validate(inputs[i])) {
          results.add(transform(inputs[i]));
        }
      } catch (e) {
        errors.add((i, e.toString()));
      }
    }

    if (errors.isNotEmpty) {
      print('$name: ${errors.length} errors during batch transform');
    }
    return results;
  }

  bool validate(T input) => true;
}

// مُحوِّل ملموس: سطر CSV من String إلى Order
class Order {
  final String id;
  final double amount;
  Order({required this.id, required this.amount});
  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)})';
}

class CsvOrderTransformer extends DataTransformer<String, Order> {
  @override
  String get name => 'CsvOrderTransformer';

  @override
  bool validate(String input) {
    final parts = input.split(',');
    return parts.length >= 2;
  }

  @override
  Order transform(String input) {
    final parts = input.split(',');
    return Order(
      id: parts[0].trim(),
      amount: double.parse(parts[1].trim()),
    );
  }
}

void main() {
  final transformer = CsvOrderTransformer();
  final csvLines = [
    'ORD-001, 1500.0',
    'ORD-002, 3200.0',
    'ORD-003, 890.0',
  ];

  final orders = transformer.batch(csvLines);
  print('Transformed ${orders.length} orders:');
  for (final order in orders) {
    print('  $order');
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

9. المثال الكامل: إطار المعالجة العامة لخط أنابيب البيانات

DART
// ============================================
// إطار المعالجة العامة لخط أنابيب البيانات
// خط أنابيب تحويل بيانات آمن النوع
// ============================================

// نوع النتيجة لمعالجة الأخطاء الآمنة
class Result<T> {
  final T? data;
  final String? error;

  Result.success(this.data) : error = null;
  Result.failure(this.error) : data = null;

  bool get isSuccess => data != null;

  Result<R> map<R>(R Function(T) fn) {
    if (isSuccess && data != null) return Result.success(fn(data!));
    return Result.failure(error);
  }
}

// مُحوِّل عام
abstract class DataTransformer<T, R> {
  R transform(T input);
  String get name;
  bool validate(T input) => true;

  Result<R> safeTransform(T input) {
    try {
      if (!validate(input)) {
        return Result.failure('$name: Validation failed');
      }
      return Result.success(transform(input));
    } catch (e) {
      return Result.failure('$name: $e');
    }
  }

  List<Result<R>> batch(List<T> inputs) =>
      inputs.map(safeTransform).toList();
}

// مُجمِّع عام
abstract class DataAggregator<T, R> {
  R aggregate(List<T> items);
  String get name;
}

// مُجمِّع مجموع للأنواع العددية
class SumAggregator<T extends num> extends DataAggregator<T, double> {
  @override
  String get name => 'SumAggregator';

  @override
  double aggregate(List<T> items) =>
      items.fold(0.0, (sum, item) => sum + item.toDouble());
}

// مُجمِّع المتوسط
class AverageAggregator<T extends num> extends DataAggregator<T, double> {
  @override
  String get name => 'AverageAggregator';

  @override
  double aggregate(List<T> items) =>
      items.isEmpty ? 0 : items.fold(0.0, (s, i) => s + i.toDouble()) / items.length;
}

// خط أنابيب يربط المُحوِّلات
class Pipeline<I, M, O> {
  final DataTransformer<I, M> _first;
  final DataTransformer<M, O> _second;

  Pipeline(this._first, this._second);

  List<Result<O>> process(List<I> inputs) {
    final midResults = _first.batch(inputs);
    final midValues = midResults.where((r) => r.isSuccess).map((r) => r.data!).toList();
    return _second.batch(midValues);
  }
}

// أنواع ملموسة
class Order {
  final String id;
  final double amount;
  Order({required this.id, required this.amount});
  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)})';
}

class OrderSummary {
  final String id;
  final String tier;
  OrderSummary({required this.id, required this.tier});
  @override
  String toString() => 'OrderSummary($id, $tier)';
}

class CsvToOrder extends DataTransformer<String, Order> {
  @override
  String get name => 'CsvToOrder';
  @override
  Order transform(String input) {
    final parts = input.split(',');
    return Order(id: parts[0].trim(), amount: double.parse(parts[1].trim()));
  }
  @override
  bool validate(String input) => input.split(',').length >= 2;
}

class OrderToSummary extends DataTransformer<Order, OrderSummary> {
  @override
  String get name => 'OrderToSummary';
  @override
  OrderSummary transform(Order input) {
    final tier = input.amount >= 1000 ? 'Premium' : 'Standard';
    return OrderSummary(id: input.id, tier: tier);
  }
}

void main() {
  final csvLines = [
    'ORD-001, 1500.0',
    'ORD-002, 50.0',
    'ORD-003, 3200.0',
  ];

  // مُحوِّل بسيط
  final parser = CsvToOrder();
  final orders = parser.batch(csvLines);

  print('=== Parsed Orders ===');
  for (final result in orders) {
    if (result.isSuccess) {
      print('  ${result.data}');
    } else {
      print('  Error: ${result.error}');
    }
  }

  // خط أنابيب: CSV ← Order ← Summary
  final pipeline = Pipeline<String, Order, OrderSummary>(
    CsvToOrder(), OrderToSummary());
  final summaries = pipeline.process(csvLines);

  print('\n=== Order Summaries ===');
  for (final result in summaries) {
    if (result.isSuccess) print('  ${result.data}');
  }

  // التجميع
  final validAmounts = orders
      .where((r) => r.isSuccess)
      .map((r) => r.data!.amount)
      .toList();

  final total = SumAggregator().aggregate(validAmounts);
  final avg = AverageAggregator().aggregate(validAmounts);
  print('\nRevenue: \$${total.toStringAsFixed(2)} USD');
  print('Average: \$${avg.toStringAsFixed(2)} USD');
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع أمثلة Dart مبنية على Dart 3.x / Flutter 3.x، قد تختلف النتائج قليلاً مع إصدار SDK.

الإخراج:

TEXT 📖 للعرض فقط
=== Parsed Orders ===
  Order(ORD-001, $1500.00)
  Order(ORD-002, $50.00)
  Order(ORD-003, $3200.00)

=== Order Summaries ===
  OrderSummary(ORD-001, Premium)
  OrderSummary(ORD-002, Standard)
  OrderSummary(ORD-003, Premium)

Revenue: $4750.00 USD
Average: $1583.33 USD

❓ أسئلة شائعة

س: هل معاملات النوع العامة موجودة في وقت التشغيل؟ ج: نعم، الأنواع العامة في Dart محققة (reified). يمكنك الحصول على معلومات النوع في وقت التشغيل. list is List<int> يُرجع true في وقت التشغيل، على عكس محو النوع في Java.

س: هل يمكن أن يكون للقيد العام حدود متعددة؟ ج: نعم، يمكنك استخدام صياغة T extends A & B (على الرغم من أن Dart لا تدعم حالياً & للحدود المتعددة مباشرة). يمكنك تحقيق ذلك بشكل غير مباشر بجعل النوع المقيد ينفذ واجهات متعددة، أو استخدام حدود مثل T extends Comparable<num>.

س: متى أستخدم الأنواع العامة مقابل dynamic؟ ج: استخدم الأنواع العامة كلما أمكن. الأنواع العامة تفحص الأنواع في وقت الترجمة، بينما dynamic يكشف الأخطاء في وقت التشغيل فقط. استخدم dynamic فقط عندما يكون النوع غير معروف حقاً (مثل تحليل JSON).

س: هل يمكن أن يكون للدالة العامة معاملات نوع مختلفة عن فئتها؟ ج: نعم. على سبيل المثال، <T, R> R transform(T input, R Function(T) fn) لها معاملا نوع. معاملات نوع الطريقة مستقلة عن معاملات نوع فئتها.

س: ما الفرق بين List<dynamic> و List<T>؟ ج: List<dynamic> يقبل عناصر من أي نوع، ويفقد أمان النوع. List<T> يقبل فقط عناصر من النوع T، مع أمان نوع مضمون من المترجم. List<dynamic> ليس نوعاً فائقاً لأنواع List<T> الأخرى.

س: هل هناك فرق بين extends و implements في القيود العامة؟ ج: في القيود، extends يطابق كل من وراثة الفئة وتنفيذ الواجهة. T extends Comparable يعني أن T يمكن أن يكون فئة فرعية أو فئة منفذة لـ Comparable.

س: ما فائدة الكلمة المفتاحية covariant؟ ج: covariant تسمح لنوع فرعي باستخدام نوع أكثر تحديداً لمعامل (التغاير). تُستخدم غالباً للمعاملات في أنماط المستهلكين لكنها تُضعف أمان النوع—استخدمها بحذر.


📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): نفّذ فئة عامة Pair<T, U> بحقول first و second، وطريقة swap() تُرجع Pair جديد بقيم مبدلة. اختبر مع Pair<int, String> و Pair<double, bool>.
  2. متوسط (صعوبة ⭐⭐): نفّذ نوع Result<T> (مشابه لـ Result في Rust) مع isSuccess و data و error، وطريقة map<R>(). استخدمه لإعادة كتابة دالة تحليل يمكن أن تفشل.
  3. تحدي (صعوبة ⭐⭐⭐): نفّذ فئة عامة Pipeline<T, R> تدعم ربط عدة DataTransformers، وتستنتج تلقائياً الأنواع الوسيطة، وأخيراً تستدعي process() لتنفيذ خط الأنابيب بالكامل. تلميح: يمكنك استخدام الأنواع العودية أو نمط الباني.

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

Web-Tutorial.com

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

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

100%