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

مشروع التخرج في OOP: بناء تطبيق كامل

60 دقيقة الدرس 24 من 24

نظرة عامة على مشروع التخرج

تهانينا على وصولك إلى الدرس الأخير من دورة Dart OOP! في هذا المشروع، ستبني نظام إدارة مهام كامل يستخدم كل مفهوم OOP تعلمته: الفئات، الوراثة، الواجهات، الفئات المجردة، المزيجات، الأنواع العامة، الفئات المختومة، أنماط التصميم، التركيب، معالجة الأخطاء، عدم القابلية للتغيير، ومبادئ SOLID. هذه بنية مشروع واقعية ستراها في تطبيق Dart أو Flutter إنتاجي.

سنبني النظام خطوة بخطوة، مع شرح أي مبدأ OOP يُظهره كل جزء.

الخطوة 1: نماذج النطاق (كائنات القيمة والكيانات)

أولاً، نُعرّف أنواع البيانات الأساسية. كائنات القيمة تمثل مفاهيم بدون هوية. الكيانات لها معرفات فريدة. جميع النماذج غير قابلة للتغيير مع دعم copyWith.

نماذج النطاق الأساسية

// ---- كائنات القيمة ----

enum Priority implements Comparable<Priority> {
  low(1),
  medium(2),
  high(3),
  critical(4);

  final int level;
  const Priority(this.level);

  @override
  int compareTo(Priority other) => level.compareTo(other.level);

  bool operator >(Priority other) => level > other.level;
  bool operator <(Priority other) => level < other.level;
}

enum TaskStatus {
  todo,
  inProgress,
  review,
  done,
  cancelled;

  bool get isActive => this == todo || this == inProgress || this == review;
  bool get isCompleted => this == done;
}

class Tag {
  final String name;
  final String color;

  const Tag(this.name, {this.color = '#808080'});

  @override
  bool operator ==(Object other) =>
      other is Tag && other.name == name;

  @override
  int get hashCode => name.hashCode;

  @override
  String toString() => name;
}

// ---- كيان ----

class Task {
  final String id;
  final String title;
  final String description;
  final Priority priority;
  final TaskStatus status;
  final List<Tag> tags;
  final DateTime createdAt;
  final DateTime? dueDate;
  final DateTime? completedAt;
  final String? assigneeId;

  const Task({
    required this.id,
    required this.title,
    this.description = '',
    this.priority = Priority.medium,
    this.status = TaskStatus.todo,
    this.tags = const [],
    required this.createdAt,
    this.dueDate,
    this.completedAt,
    this.assigneeId,
  });

  bool get isOverdue =>
      dueDate != null &&
      status.isActive &&
      DateTime.now().isAfter(dueDate!);

  Task copyWith({
    String? title,
    String? description,
    Priority? priority,
    TaskStatus? status,
    List<Tag>? tags,
    DateTime? dueDate,
    DateTime? completedAt,
    String? assigneeId,
  }) => Task(
    id: id,
    title: title ?? this.title,
    description: description ?? this.description,
    priority: priority ?? this.priority,
    status: status ?? this.status,
    tags: tags ?? this.tags,
    createdAt: createdAt,
    dueDate: dueDate ?? this.dueDate,
    completedAt: completedAt ?? this.completedAt,
    assigneeId: assigneeId ?? this.assigneeId,
  );

  @override
  bool operator ==(Object other) => other is Task && other.id == id;

  @override
  int get hashCode => id.hashCode;

  @override
  String toString() => 'Task($id: $title [$status] $priority)';
}
مبادئ OOP المُستخدمة: التغليف (خصائص محسوبة مثل isOverdueعدم القابلية للتغيير (جميع الحقول final، نمط copyWith)، كائنات القيمة (Tag, Priority)، الكيان (Task بمساواة مبنية على ID)، تعدادات بسلوك (طرق على Priority و TaskStatus).

الخطوة 2: معالجة الأخطاء (الفئات المختومة ونمط Result)

بعد ذلك، نُعرّف نظام معالجة أخطاء قوي باستخدام فئات مختومة حتى يضمن المترجم أننا نعالج كل نوع خطأ.

أنواع الأخطاء والنتيجة

// تسلسل أخطاء مختوم -- شمولية يتحقق منها المترجم
sealed class AppError {
  final String message;
  const AppError(this.message);

  @override
  String toString() => '$runtimeType: $message';
}

class NotFoundError extends AppError {
  final String entityType;
  final String id;
  NotFoundError(this.entityType, this.id)
      : super('$entityType "$id" غير موجود');
}

class ValidationError extends AppError {
  final Map<String, String> fieldErrors;
  ValidationError(this.fieldErrors)
      : super('فشل التحقق: ${fieldErrors.entries.map((e) => "${e.key}: ${e.value}").join(", ")}');
}

class PermissionError extends AppError {
  final String action;
  final String userId;
  PermissionError({required this.action, required this.userId})
      : super('المستخدم "$userId" غير مسموح له بـ $action');
}

class ConflictError extends AppError {
  ConflictError(super.message);
}

// نوع Result عام
sealed class Result<T> {
  const Result();
}

class Success<T> extends Result<T> {
  final T value;
  const Success(this.value);
}

class Failure<T> extends Result<T> {
  final AppError error;
  const Failure(this.error);
}

// إضافات للراحة
extension ResultOps<T> on Result<T> {
  T? get valueOrNull => switch (this) {
    Success(value: var v) => v,
    Failure() => null,
  };

  Result<R> map<R>(R Function(T) f) => switch (this) {
    Success(value: var v) => Success(f(v)),
    Failure(error: var e) => Failure(e),
  };
}

الخطوة 3: الواجهات والعقود (التجريد)

نُعرّف العقود باستخدام فئات مجردة وواجهات. هذا يتبع مبدأ عكس التبعية: الوحدات عالية المستوى تعتمد على التجريدات، وليس التنفيذات الملموسة.

واجهات المستودع والخدمة

// واجهة المستودع -- تجرّد تخزين البيانات
abstract class TaskRepository {
  Future<Result<Task>> getById(String id);
  Future<Result<List<Task>>> getAll();
  Future<Result<Task>> save(Task task);
  Future<Result<void>> delete(String id);
  Future<Result<List<Task>>> findByStatus(TaskStatus status);
  Future<Result<List<Task>>> findByTag(Tag tag);
}

// واجهة الإشعارات -- تجرّد كيفية إرسال الإشعارات
abstract class NotificationService {
  Future<void> notify(String userId, String title, String message);
}

// واجهة مولد المعرفات
abstract class IdGenerator {
  String generate();
}

// واجهة المُحقق باستخدام الأنواع العامة
abstract class Validator<T> {
  Result<T> validate(T value);
}
SOLID - عكس التبعية: بتعريف TaskRepository كفئة مجردة، منطق الأعمال لا يعرف أبداً إذا كانت المهام مخزنة في الذاكرة أو قاعدة بيانات أو API بعيد. يمكنك تبديل التنفيذات دون تغيير سطر واحد من كود الأعمال.

الخطوة 4: التنفيذات الملموسة (الوراثة والتركيب)

الآن ننفذ الواجهات. المستودع في الذاكرة مثالي للاختبار؛ مستودع قاعدة بيانات أو API سيُستخدم في الإنتاج.

تنفيذ المستودع في الذاكرة

// مولد معرفات بسيط
class UuidGenerator implements IdGenerator {
  int _counter = 0;

  @override
  String generate() {
    _counter++;
    final timestamp = DateTime.now().millisecondsSinceEpoch;
    return 'task_${timestamp}_$_counter';
  }
}

// مستودع في الذاكرة -- مثالي للاختبار
class InMemoryTaskRepository implements TaskRepository {
  final Map<String, Task> _store = {};

  @override
  Future<Result<Task>> getById(String id) async {
    final task = _store[id];
    if (task == null) {
      return Failure(NotFoundError('Task', id));
    }
    return Success(task);
  }

  @override
  Future<Result<List<Task>>> getAll() async {
    return Success(_store.values.toList());
  }

  @override
  Future<Result<Task>> save(Task task) async {
    _store[task.id] = task;
    return Success(task);
  }

  @override
  Future<Result<void>> delete(String id) async {
    if (!_store.containsKey(id)) {
      return Failure(NotFoundError('Task', id));
    }
    _store.remove(id);
    return Success(null as void);
  }

  @override
  Future<Result<List<Task>>> findByStatus(TaskStatus status) async {
    final tasks = _store.values
        .where((t) => t.status == status)
        .toList();
    return Success(tasks);
  }

  @override
  Future<Result<List<Task>>> findByTag(Tag tag) async {
    final tasks = _store.values
        .where((t) => t.tags.contains(tag))
        .toList();
    return Success(tasks);
  }
}

// خدمة إشعارات وحدة التحكم
class ConsoleNotificationService implements NotificationService {
  @override
  Future<void> notify(String userId, String title, String message) async {
    print('[إشعار لـ $userId] $title: $message');
  }
}

الخطوة 5: التحقق (المسؤولية الواحدة والأنواع العامة)

كل مُحقق لديه وظيفة واحدة: التحقق من شيء واحد. يمكن تركيب المُحققات معاً. هذا يوضح مبدأ المسؤولية الواحدة ونمط الاستراتيجية.

التحقق من المهام

class TaskValidator implements Validator<Task> {
  @override
  Result<Task> validate(Task task) {
    final errors = <String, String>{};

    if (task.title.trim().isEmpty) {
      errors['title'] = 'العنوان لا يمكن أن يكون فارغاً';
    }
    if (task.title.length > 200) {
      errors['title'] = 'العنوان لا يمكن أن يتجاوز 200 حرف';
    }
    if (task.dueDate != null && task.dueDate!.isBefore(task.createdAt)) {
      errors['dueDate'] = 'تاريخ الاستحقاق لا يمكن أن يكون قبل تاريخ الإنشاء';
    }
    if (task.tags.length > 10) {
      errors['tags'] = 'لا يمكن أن يكون لديك أكثر من 10 وسوم';
    }

    if (errors.isNotEmpty) {
      return Failure(ValidationError(errors));
    }
    return Success(task);
  }
}

// مُحقق انتقال الحالة -- يفرض قواعد العمل
class StatusTransitionValidator {
  // انتقالات صالحة: todo->inProgress، inProgress->review، إلخ
  static final Map<TaskStatus, Set<TaskStatus>> _validTransitions = {
    TaskStatus.todo: {TaskStatus.inProgress, TaskStatus.cancelled},
    TaskStatus.inProgress: {TaskStatus.review, TaskStatus.todo, TaskStatus.cancelled},
    TaskStatus.review: {TaskStatus.done, TaskStatus.inProgress},
    TaskStatus.done: {},      // حالة نهائية
    TaskStatus.cancelled: {}, // حالة نهائية
  };

  Result<void> validateTransition(TaskStatus from, TaskStatus to) {
    final valid = _validTransitions[from] ?? {};
    if (!valid.contains(to)) {
      return Failure(ConflictError(
        'لا يمكن الانتقال من $from إلى $to. الانتقالات الصالحة: $valid',
      ));
    }
    return Success(null as void);
  }
}

الخطوة 6: المزيجات (الاهتمامات المتقاطعة)

المزيجات تضيف سلوكاً قابلاً لإعادة الاستخدام بدون وراثة. هنا ننشئ مزيجات للتسجيل والتتبع الزمني -- سلوكيات قد تحتاجها خدمات متعددة.

مزيجات قابلة لإعادة الاستخدام

mixin Logging {
  String get logPrefix;

  void logInfo(String message) {
    print('[معلومات][$logPrefix] $message');
  }

  void logWarning(String message) {
    print('[تحذير][$logPrefix] $message');
  }

  void logError(String message) {
    print('[خطأ][$logPrefix] $message');
  }
}

mixin EventTracking {
  final List<String> _events = [];

  void trackEvent(String event) {
    _events.add('[${DateTime.now().toIso8601String()}] $event');
  }

  List<String> get eventHistory => List.unmodifiable(_events);
}

mixin Statistics {
  int _operationCount = 0;
  final Stopwatch _stopwatch = Stopwatch();

  void startTimer() => _stopwatch.start();
  void stopTimer() => _stopwatch.stop();
  void incrementOps() => _operationCount++;

  int get operationCount => _operationCount;
  Duration get totalTime => _stopwatch.elapsed;
  double get avgTimePerOp =>
      _operationCount > 0
          ? _stopwatch.elapsedMilliseconds / _operationCount
          : 0;
}

الخطوة 7: طبقة الخدمة (التركيب ونمط الواجهة)

TaskService هي قلب التطبيق. تُركّب مستودعاً ومُحققات وخدمة إشعارات ومولد معرفات. تستخدم مزيجات للتسجيل والتتبع. هذا هو نمط الواجهة (Facade) -- واجهة بسيطة لنظام فرعي معقد.

خدمة المهام -- منطق الأعمال الأساسي

class TaskService with Logging, EventTracking, Statistics {
  final TaskRepository _repository;
  final NotificationService _notifications;
  final IdGenerator _idGenerator;
  final TaskValidator _validator = TaskValidator();
  final StatusTransitionValidator _transitionValidator = StatusTransitionValidator();

  @override
  String get logPrefix => 'TaskService';

  TaskService({
    required TaskRepository repository,
    required NotificationService notifications,
    required IdGenerator idGenerator,
  })  : _repository = repository,
        _notifications = notifications,
        _idGenerator = idGenerator;

  // إنشاء مهمة جديدة
  Future<Result<Task>> createTask({
    required String title,
    String description = '',
    Priority priority = Priority.medium,
    List<Tag> tags = const [],
    DateTime? dueDate,
    String? assigneeId,
  }) async {
    startTimer();
    incrementOps();

    final task = Task(
      id: _idGenerator.generate(),
      title: title,
      description: description,
      priority: priority,
      tags: tags,
      createdAt: DateTime.now(),
      dueDate: dueDate,
      assigneeId: assigneeId,
    );

    // التحقق
    final validation = _validator.validate(task);
    if (validation is Failure<Task>) {
      logWarning('فشل التحقق للمهمة: ${task.title}');
      stopTimer();
      return validation;
    }

    // الحفظ
    final result = await _repository.save(task);
    if (result is Success<Task>) {
      logInfo('تم إنشاء المهمة: ${task.id} - ${task.title}');
      trackEvent('task_created:${task.id}');

      // إشعار المُكلّف
      if (assigneeId != null) {
        await _notifications.notify(
          assigneeId,
          'مهمة جديدة مُسندة',
          'تم إسناد مهمة لك: ${task.title}',
        );
      }
    }

    stopTimer();
    return result;
  }

  // تحديث حالة المهمة مع التحقق
  Future<Result<Task>> updateStatus(String taskId, TaskStatus newStatus) async {
    incrementOps();

    // الحصول على المهمة الحالية
    final getResult = await _repository.getById(taskId);
    if (getResult is Failure<Task>) return getResult;

    final task = (getResult as Success<Task>).value;

    // التحقق من الانتقال
    final transitionResult = _transitionValidator.validateTransition(
      task.status, newStatus,
    );
    if (transitionResult is Failure) {
      return Failure((transitionResult as Failure).error);
    }

    // تطبيق التحديث
    final updatedTask = task.copyWith(
      status: newStatus,
      completedAt: newStatus == TaskStatus.done ? DateTime.now() : null,
    );

    final saveResult = await _repository.save(updatedTask);
    if (saveResult is Success<Task>) {
      logInfo('المهمة ${task.id}: ${task.status} -> $newStatus');
      trackEvent('status_changed:${task.id}:$newStatus');

      if (task.assigneeId != null && newStatus == TaskStatus.done) {
        await _notifications.notify(
          task.assigneeId!,
          'اكتملت المهمة',
          '"${task.title}" تم تعليمها كمُنجزة.',
        );
      }
    }

    return saveResult;
  }

  // الحصول على إحصائيات المهام
  Future<TaskStats> getStatistics() async {
    final result = await _repository.getAll();
    final tasks = result.valueOrNull ?? [];

    return TaskStats(
      total: tasks.length,
      byStatus: {
        for (var status in TaskStatus.values)
          status: tasks.where((t) => t.status == status).length,
      },
      byPriority: {
        for (var p in Priority.values)
          p: tasks.where((t) => t.priority == p).length,
      },
      overdue: tasks.where((t) => t.isOverdue).length,
    );
  }

  // البحث في المهام مع التصفية
  Future<Result<List<Task>>> searchTasks({
    String? titleContains,
    TaskStatus? status,
    Priority? minPriority,
    Tag? tag,
  }) async {
    final result = await _repository.getAll();
    if (result is Failure<List<Task>>) return result;

    var tasks = (result as Success<List<Task>>).value;

    if (titleContains != null) {
      tasks = tasks.where((t) =>
          t.title.toLowerCase().contains(titleContains.toLowerCase())
      ).toList();
    }
    if (status != null) {
      tasks = tasks.where((t) => t.status == status).toList();
    }
    if (minPriority != null) {
      tasks = tasks.where((t) => t.priority >= minPriority).toList();
    }
    if (tag != null) {
      tasks = tasks.where((t) => t.tags.contains(tag)).toList();
    }

    return Success(tasks);
  }
}

// كائن قيمة الإحصائيات
class TaskStats {
  final int total;
  final Map<TaskStatus, int> byStatus;
  final Map<Priority, int> byPriority;
  final int overdue;

  const TaskStats({
    required this.total,
    required this.byStatus,
    required this.byPriority,
    required this.overdue,
  });

  double get completionRate =>
      total > 0 ? (byStatus[TaskStatus.done] ?? 0) / total * 100 : 0;

  @override
  String toString() => 'TaskStats(الإجمالي: $total, مُنجز: ${byStatus[TaskStatus.done]}, '
      'متأخر: $overdue, نسبة الإنجاز: ${completionRate.toStringAsFixed(1)}%)';
}
مبادئ OOP في الخدمة: التركيب (الخدمة لديها مستودع وإشعارات إلخ)، نمط الواجهة (طرق بسيطة تخفي عمليات معقدة)، المزيجات (Logging و EventTracking و Statistics)، المفتوح/المغلق (إضافة سلوك جديد عبر طرق جديدة دون تعديل الموجود)، تحديثات غير قابلة للتغيير (copyWith على المهام).

الخطوة 8: تجميع كل شيء معاً

الآن لنربط كل شيء معاً ونوضح النظام الكامل وهو يعمل.

تشغيل التطبيق الكامل

Future<void> main() async {
  // ---- ربط التبعيات (حقن التبعيات) ----
  final repository = InMemoryTaskRepository();
  final notifications = ConsoleNotificationService();
  final idGenerator = UuidGenerator();

  final taskService = TaskService(
    repository: repository,
    notifications: notifications,
    idGenerator: idGenerator,
  );

  // ---- إنشاء بعض المهام ----
  print('=== إنشاء المهام ===');

  final result1 = await taskService.createTask(
    title: 'تصميم مخطط قاعدة البيانات',
    description: 'إنشاء ERD وتعريف جميع الجداول',
    priority: Priority.high,
    tags: [Tag('backend'), Tag('database')],
    dueDate: DateTime.now().add(Duration(days: 7)),
    assigneeId: 'user_alice',
  );

  final result2 = await taskService.createTask(
    title: 'بناء صفحة تسجيل الدخول',
    priority: Priority.critical,
    tags: [Tag('frontend'), Tag('auth')],
    assigneeId: 'user_bob',
  );

  final result3 = await taskService.createTask(
    title: 'كتابة اختبارات الوحدة',
    priority: Priority.medium,
    tags: [Tag('testing')],
  );

  // ---- التحقق عملياً ----
  print('\n=== التحقق ===');
  final invalidResult = await taskService.createTask(title: '');
  switch (invalidResult) {
    case Success(value: var task):
      print('تم الإنشاء: $task');
    case Failure(error: var error):
      print('خطأ متوقع: $error');
  }

  // ---- تحديث حالة المهمة ----
  print('\n=== انتقالات الحالة ===');
  final taskId = (result1 as Success<Task>).value.id;

  await taskService.updateStatus(taskId, TaskStatus.inProgress);
  await taskService.updateStatus(taskId, TaskStatus.review);
  await taskService.updateStatus(taskId, TaskStatus.done);

  // انتقال غير صالح
  final invalidTransition = await taskService.updateStatus(
    taskId, TaskStatus.inProgress,
  );
  switch (invalidTransition) {
    case Success():
      print('نجاح غير متوقع');
    case Failure(error: var e):
      print('متوقع: $e');
  }

  // ---- البحث والتصفية ----
  print('\n=== البحث ===');
  final highPriority = await taskService.searchTasks(
    minPriority: Priority.high,
  );
  switch (highPriority) {
    case Success(value: var tasks):
      print('مهام بأولوية عالية+:');
      for (var t in tasks) {
        print('  $t');
      }
    case Failure(error: var e):
      print('خطأ: $e');
  }

  // ---- الإحصائيات ----
  print('\n=== الإحصائيات ===');
  final stats = await taskService.getStatistics();
  print(stats);

  // ---- مقاييس الخدمة ----
  print('\n=== مقاييس الخدمة ===');
  print('العمليات: ${taskService.operationCount}');
  print('سجل الأحداث: ${taskService.eventHistory.length} أحداث');
  for (var event in taskService.eventHistory) {
    print('  $event');
  }
}

مراجعة مبادئ SOLID

لنراجع كيف يتبع تطبيقنا جميع مبادئ SOLID الخمسة:

مبادئ SOLID في نظامنا

// S - مبدأ المسؤولية الواحدة
// كل فئة لديها سبب واحد للتغيير:
// - Task: نموذج البيانات
// - TaskValidator: قواعد التحقق
// - StatusTransitionValidator: قواعد الانتقال
// - InMemoryTaskRepository: تخزين البيانات
// - ConsoleNotificationService: إرسال الإشعارات
// - TaskService: تنسيق منطق الأعمال

// O - مبدأ المفتوح/المغلق
// مفتوح للتوسيع، مغلق للتعديل:
// - إضافة مستودع جديد: class PostgresRepository implements TaskRepository
// - إضافة إشعارات جديدة: class SlackNotification implements NotificationService
// - إضافة مُحققات جديدة: class DueDateValidator implements Validator<Task>
// لا شيء من هذا يتطلب تعديل الكود الموجود!

// L - مبدأ استبدال ليسكوف
// أي تنفيذ يمكنه استبدال واجهته:
// TaskRepository repo = InMemoryTaskRepository();
// TaskRepository repo = PostgresTaskRepository();  // بديل مباشر
// TaskService يعمل بشكل مطابق مع أي منهما.

// I - مبدأ فصل الواجهات
// الواجهات صغيرة ومركزة:
// - TaskRepository: عمليات CRUD فقط
// - NotificationService: notify() فقط
// - IdGenerator: generate() فقط
// - Validator<T>: validate() فقط
// لا فئة مجبرة على تنفيذ طرق لا تحتاجها.

// D - مبدأ عكس التبعية
// المستوى العالي (TaskService) يعتمد على تجريدات (TaskRepository)،
// وليس فئات ملموسة (InMemoryTaskRepository).
// التبعيات تُحقن عبر المُنشئ.
// هذا يجعل الاختبار سهلاً -- فقط احقن نسخ وهمية!

اختبار النظام

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

الاختبار بتنفيذات وهمية

// خدمة إشعارات وهمية للاختبار
class MockNotificationService implements NotificationService {
  final List<({String userId, String title, String message})> sent = [];

  @override
  Future<void> notify(String userId, String title, String message) async {
    sent.add((userId: userId, title: title, message: message));
  }
}

// مولد معرفات ثابت لاختبارات حتمية
class FixedIdGenerator implements IdGenerator {
  final List<String> _ids;
  int _index = 0;

  FixedIdGenerator(this._ids);

  @override
  String generate() => _ids[_index++];
}

// مثال اختبار
Future<void> testCreateTask() async {
  // الترتيب -- إنشاء خدمة بتبعيات اختبار
  final repo = InMemoryTaskRepository();
  final notifications = MockNotificationService();
  final idGen = FixedIdGenerator(['test-id-1', 'test-id-2']);

  final service = TaskService(
    repository: repo,
    notifications: notifications,
    idGenerator: idGen,
  );

  // التنفيذ -- إنشاء مهمة
  final result = await service.createTask(
    title: 'مهمة اختبار',
    assigneeId: 'user_123',
  );

  // التأكد
  switch (result) {
    case Success(value: var task):
      assert(task.id == 'test-id-1');
      assert(task.title == 'مهمة اختبار');
      assert(task.status == TaskStatus.todo);
      assert(notifications.sent.length == 1);
      assert(notifications.sent.first.userId == 'user_123');
      print('جميع التأكيدات نجحت!');
    case Failure(error: var e):
      print('فشل الاختبار: $e');
  }
}
أفضل ممارسة: صمم دائماً لقابلية الاختبار من البداية. إذا كان صعب اختبار فئة، فمن المحتمل أن لديها مسؤوليات كثيرة أو تبعيات ملموسة كثيرة. استخدم حقن المُنشئ وبرمج ضد الواجهات، وسيكون كودك قابلاً للاختبار ومرناً.

ملخص العمارة

إليك ملخص عمارة النظام الكامل وأي مفاهيم OOP تستخدمها كل طبقة:

خريطة العمارة الكاملة

// الطبقة 1: نماذج النطاق
// المفاهيم: الفئات، التعدادات، كائنات القيمة، الكيانات، عدم القابلية للتغيير، copyWith
// الملفات: task.dart, priority.dart, tag.dart, task_status.dart

// الطبقة 2: معالجة الأخطاء
// المفاهيم: الفئات المختومة، الأنواع العامة، مطابقة الأنماط، نمط Result
// الملفات: app_error.dart, result.dart

// الطبقة 3: العقود (الواجهات)
// المفاهيم: الفئات المجردة، الأنواع العامة، عكس التبعية
// الملفات: task_repository.dart, notification_service.dart, validator.dart

// الطبقة 4: التنفيذات
// المفاهيم: الوراثة (implements)، التغليف، المسؤولية الواحدة
// الملفات: in_memory_repo.dart, console_notifications.dart, uuid_generator.dart

// الطبقة 5: التحقق
// المفاهيم: نمط الاستراتيجية، المسؤولية الواحدة، التركيب
// الملفات: task_validator.dart, status_transition_validator.dart

// الطبقة 6: الاهتمامات المتقاطعة
// المفاهيم: المزيجات، فصل الاهتمامات
// الملفات: logging.dart, event_tracking.dart, statistics.dart

// الطبقة 7: طبقة الخدمة (منطق الأعمال)
// المفاهيم: نمط الواجهة، التركيب، حقن التبعيات، المزيجات
// الملفات: task_service.dart

// الطبقة 8: نقطة دخول التطبيق
// المفاهيم: حقن التبعيات، الربط
// الملفات: main.dart

// ملخص مفاهيم OOP:
// - الفئات والكائنات ............ Task, Tag, TaskService
// - التغليف ..................... حقول خاصة (_store, _items)
// - الوراثة ..................... implements TaskRepository
// - تعدد الأشكال ................ NotificationService (تنفيذات متعددة)
// - التجريد ..................... فئات مجردة (TaskRepository, Validator)
// - الأنواع العامة .............. Result<T>, Validator<T>
// - الفئات المختومة ............. تسلسل AppError
// - المزيجات .................... Logging, EventTracking, Statistics
// - تعدادات بسلوك ............... Priority, TaskStatus
// - عدم القابلية للتغيير و copyWith ... Task, TaskStats
// - كائنات القيمة مقابل الكيانات .. Tag مقابل Task
// - أنماط التصميم ............... Facade, Strategy, Repository, Observer
// - مبادئ SOLID ................ الخمسة موضحة
// - معالجة الأخطاء .............. نمط Result، أخطاء مختومة
// - حقن التبعيات ................ حقن المُنشئ في TaskService
ما أنجزته: لقد بنيت عمارة تطبيق كاملة بجودة احترافية باستخدام كل مفهوم OOP رئيسي في Dart. هذا النمط بالضبط -- نماذج النطاق، أنواع الأخطاء، الواجهات، التنفيذات، التحقق، الخدمات -- هو كيف تُبنى تطبيقات Dart و Flutter الإنتاجية الحقيقية. أنت الآن جاهز لبناء أي شيء.