Menu
CoddyTech

نمط المستودع

جزء من قسم البرمجة كائنية التوجه في رحلة Dart على Coddy. الدرس 100 من 110.

يفصل نمط المستودع منطق الوصول إلى البيانات عن منطق الأعمال من خلال إنشاء طبقة تجريد بين تطبيقك ومصادر البيانات. وبدلًا من جعل أصنافك تتفاعل مباشرةً مع قواعد البيانات أو واجهات API أو الملفات، فإنها تعمل مع مستودع يتولى جميع عمليات البيانات.

يعرّف النمط واجهة لعمليات البيانات، وتتولى التطبيقات الملموسة آلية التخزين الفعلية. وهذا يسهّل تبديل مصادر البيانات دون تغيير منطق الأعمال لديك:

// كيان
class User {
  final int id;
  final String name;
  User(this.id, this.name);
}

// واجهة المستودع
abstract class UserRepository {
  User? getById(int id);
  List<User> getAll();
  void save(User user);
  void delete(int id);
}

// تنفيذ في الذاكرة
class InMemoryUserRepository implements UserRepository {
  final Map<int, User> _users = {};

  @override
  User? getById(int id) => _users[id];

  @override
  List<User> getAll() => _users.values.toList();

  @override
  void save(User user) => _users[user.id] = user;

  @override
  void delete(int id) => _users.remove(id);
}

void main() {
  UserRepository repo = InMemoryUserRepository();
  
  repo.save(User(1, 'Alice'));
  repo.save(User(2, 'Bob'));
  
  print(repo.getById(1)?.name);  // Alice
  print(repo.getAll().length);    // 2
}

يعتمد منطق عملك فقط على الواجهة UserRepository. يمكنك لاحقًا إنشاء DatabaseUserRepository أو ApiUserRepository يطبّق الواجهة نفسها، واستبدال التطبيق دون تعديل أي كود يستخدم المستودع. يُعد هذا النمط أساسيًا للتطبيقات القابلة للاختبار والصيانة، حيث قد تتغير مصادر البيانات بمرور الوقت.

challenge icon

التحدي

سهل

لننشئ نظامًا لمخزون المنتجات باستخدام نمط المستودع! ستنشئ طبقة تجريد تفصل طريقة تخزين المنتجات عن طريقة استخدامها، مما يسهّل تبديل تطبيقات التخزين دون تغيير منطق الأعمال.

ستنظّم شيفرتك في ملفين:

  • product_repository.dart: يحتوي هذا الملف على فئتي الكيان والمستودع. ابدأ بفئة Product التي تضم حقول final للمعرّف id (int)، والاسم name (String)، والسعر price (double). ثم عرّف فئة مجردة ProductRepository تُعلن عن واجهة عمليات البيانات: getById(int id) التي تُعيد قيمة Product قابلة لأن تكون فارغة، وgetAll() التي تُعيد قائمة بالمنتجات، وsave(Product product) لتخزين منتج، وdelete(int id) لإزالة منتج. وأخيرًا، أنشئ InMemoryProductRepository التي تطبّق هذه الواجهة باستخدام Map<int, Product> للتخزين.
  • main.dart: استورد ملف المستودع واستعرض نمط المستودع عمليًا. أنشئ InMemoryProductRepository، لكن خزّنها في متغير نوعه ProductRepository، فهذا يوضح أن شيفرتك تعتمد على الواجهة لا على التطبيق. خزّن ثلاثة منتجات: Laptop (المعرّف: 1، السعر: 999.99)، وMouse (المعرّف: 2، السعر: 29.99)، وKeyboard (المعرّف: 3، السعر: 79.99). ثم استرجع المنتج ذي المعرّف 2 واطبعه بالتنسيق Found: [name] - $[price]. احذف المنتج ذي المعرّف 1. وأخيرًا، اطبع عدد المنتجات المتبقية على النحو Products in stock: [count].

تكمن قوة هذا النمط في أن شيفرتك الرئيسية لا تعرف سوى واجهة ProductRepository. يمكنك لاحقًا إنشاء DatabaseProductRepository أو ApiProductRepository واستبدالها بها دون تغيير أي جزء من منطق الأعمال!

الناتج المتوقع:

Found: Mouse - $29.99
Products in stock: 2

جرّب بنفسك

import 'product_repository.dart';

void main() {
  // أنشئ مستودعًا - لاحظ أن النوع هو الواجهة، وليس التنفيذ
  // TODO: أنشئ InMemoryProductRepository وخزّنه في متغير من النوع ProductRepository
  
  // TODO: احفظ ثلاثة منتجات:
  // - حاسوب محمول (id: 1، السعر: 999.99)
  // - فأرة (id: 2، السعر: 29.99)
  // - لوحة مفاتيح (id: 3، السعر: 79.99)
  
  // TODO: استرجع المنتج ذي id 2 واطبع: "Found: [name] - $[price]"
  
  // TODO: احذف المنتج ذي id 1
  
  // TODO: اطبع عدد المنتجات المتبقية: "Products in stock: [count]"
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Dart عبر الإنترنت