Flutter: تصميم المشروع — تخطيط بنية ShopApp

ارسم المخطط قبل البناء — تصميم البنية يحدد أين توجد "الجدران الحاملة" في كودك.

📋 المتطلبات السابقة: يجب أن تكون قد أتقنت ما يلي أولًا

1. ما ستتعلمه


2. قصة مشروع بلا بنية

(1) المشكلة: متطلبات غامضة + بنية فوضوية

عندما استلم بوب مشروع ShopApp، كان المتطلب الوحيد: "ابنِ تطبيق تجارة إلكترونية عبر الحدود." بدأ البرمجة فورًا، وبعد 3 أشهر اكتشف: قصص المستخدم غير واضحة (ميزات إدارة البائعين مفقودة)، نماذج البيانات تتغير باستمرار (أُعيدت كتابة Order ثلاث مرات)، والبنية مقترنة بشدة (تغيير الدفع أثر على قائمة المنتجات). تأخر المشروع شهرين.

(2) صمّم أولًا، برمج بعد ذلك

مرحلة تصميم المشروع توضح المتطلبات والبنية ونماذج البيانات وتخطيط واجهة المستخدم مسبقًا — ثم تصبح البرمجة "بناءً من المخطط."

(3) النتيجة: وقت البرمجة انخفض للنصف + تكلفة التغيير انخفضت بنسبة 80%

بعد قضاء أسبوعين في التصميم، أكمل بوب البرمجة في 4 أسابيع (بدلاً من 3+2 أشهر سابقًا)، وتغييرات المتطلبات تحتاج فقط تعديل الطبقة المقابلة دون التأثير على الوحدات الأخرى.


3. تحليل المتطلبات

(1) قصص المستخدم

100%
graph TD
    subgraph User Stories
        ALICE[Alice: Browse → Order → Pay $299.99]
        BOB[Bob: Manage Products → View Orders]
        CHARLIE[Charlie: Cross-border → JPY → International Payment]
    end
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
الدور قصة المستخدم الأولوية
أليس (مستهلكة) تصفح قائمة منتجات بمستوى المليون P0
أليس بحث/تصفية/تصفح بالفئة P0
أليس عرض تفاصيل المنتج + المراجعات P0
أليس إضافة للسلة + إدارة الكميات P0
أليس تسوية بعملات متعددة USD/CNY/JPY P1
أليس عرض حالة الطلب + تتبع الشحن P1
بوب (بائع) إدارة عرض/إخفاء المنتجات + المخزون P1
بوب عرض تقارير بيانات المبيعات P2
تشارلي (مستخدم عبر الحدود) تسوق عبر الحدود + دفع دولي P2
تشارلي تبديل متعدد اللغات (EN/ZH/JA) P1

4. الاختيار التقني

(1) قرارات حزمة التقنيات

المجال الاختيار المبرر
إطار واجهة المستخدم Flutter 3.x (Material 3) عبر المنصات + محرك رسم مخصص
إدارة الحالة Riverpod 2.x (@riverpod) آمن النوع + توليد كود
التوجيه GoRouter تعريفي + دعم URL للويب
الشبكات Dio + Interceptors اعتراضات + تحديث Token
الخلفية Firebase (Auth+FS+Storage) تشغيل صفري + مزامنة فورية
التخزين المؤقت Hive NoSQL خفيف + وضع عدم اتصال
التخزين المشفر flutter_secure_storage تخزين آمن للرموز
التسلسل json_serializable + freezed آمن النوع + غير قابل للتغيير
التدويل flutter_localizations + intl سير عمل ARB
الاختبار flutter_test + mocktail وحدة/ويدجت/تكامل
CI/CD GitHub Actions طبقة مجانية + مصفوفة متعددة

(2) مقارنة Riverpod مع BLoC

البُعد Riverpod BLoC
منحنى التعلم متوسط مرتفع
الكود المعياري أقل (@riverpod) أكثر (Event/State/Bloc)
أمان النوع قوي قوي
الاختبار بسيط بسيط
توليد الكود
الأنسب لـ مشاريع صغيرة-متوسطة مشاريع الفرق الكبيرة
💡 نصيحة: ShopApp اختار Riverpod لأنه يحتوي كود أقل، توليد الكود يقلل الكود المعياري، ومنحنى التعلم معتدل.


5. طبقات البنية النظيفة

100%
graph TD
    subgraph Presentation
        PAGE[Pages/Widgets]
        NOTI[Notifiers/Providers]
    end
    subgraph Domain
        ENT[Entities]
        REPO_I[Repository Interfaces]
        USECASE[Use Cases]
    end
    subgraph Data
        REPO_IMPL[Repository Impl]
        DS[Data Sources]
        DTO2[DTOs / Models]
    end
    PAGE --> NOTI
    NOTI --> USECASE
    USECASE --> REPO_I
    REPO_I -.-> REPO_IMPL
    REPO_IMPL --> DS
    DS --> DTO2
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(1) مسؤوليات الطبقات

الطبقة الدليل المسؤولية اتجاه التبعية
العرض screens/, widgets/ واجهة المستخدم + تفاعل المستخدم ← التطبيق
التطبيق notifiers/, usecases/ منطق الأعمال + الحالة ← النطاق
النطاق entities/, repositories/ قواعد الأعمال الأساسية بدون تبعيات خارجية
البيانات repositories/impl/, datasources/ جلب البيانات + الاستمرارية ← النطاق

(2) قواعد التبعية


6. نمذجة البيانات

(1) الكيانات الأساسية

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: تعريفات كيان النطاق

DART
// domain/entities/product.dart
// Pure Dart class, no external dependencies

class Product {
  final int id;
  final String name;
  final double price;
  final String imageUrl;
  final String category;
  final double rating;
  final int reviewCount;
  final int stock;
  final String currency;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
    this.category = 'General',
    this.rating = 0.0,
    this.reviewCount = 0,
    this.stock = 0,
    this.currency = 'USD',
  });

  bool get inStock => stock > 0;
  bool get hasDiscount => false; // Extended in sale scenario
}

// domain/entities/cart_item.dart
class CartItem {
  final Product product;
  final int quantity;
  final String? selectedSize;
  final String? selectedColor;

  const CartItem({
    required this.product,
    required this.quantity,
    this.selectedSize,
    this.selectedColor,
  });

  double get lineTotal => product.price * quantity;

  CartItem copyWith({int? quantity, String? selectedSize, String? selectedColor}) =>
      CartItem(product: product, quantity: quantity ?? this.quantity,
        selectedSize: selectedSize ?? this.selectedSize,
        selectedColor: selectedColor ?? this.selectedColor);
}

// domain/entities/address.dart
class Address {
  final String name;
  final String street;
  final String city;
  final String state;
  final String zip;
  final String country;

  const Address({
    required this.name,
    required this.street,
    required this.city,
    required this.state,
    required this.zip,
    this.country = 'US',
  });

  String get fullAddress => '$street, $city, $state $zip, $country';

  Map<String, dynamic> toJson() => {
    'name': name, 'street': street, 'city': city,
    'state': state, 'zip': zip, 'country': country,
  };
}

// domain/entities/order.dart
enum OrderStatus { pending, confirmed, shipped, delivered, cancelled }

class Order {
  final String id;
  final String userId;
  final List<CartItem> items;
  final double total;
  final String currency;
  final OrderStatus status;
  final DateTime createdAt;
  final Address shippingAddress;

  const Order({
    required this.id,
    required this.userId,
    required this.items,
    required this.total,
    this.currency = 'USD',
    this.status = OrderStatus.pending,
    required this.createdAt,
    required this.shippingAddress,
  });
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) تصميم مخطط Firestore

TEXT
firestore/
├── products/{productId}
│   ├── name: string
│   ├── price: number
│   ├── stock: number
│   ├── category: string
│   ├── rating: number
│   ├── imageUrl: string
│   └── currency: string
├── users/{userId}
│   ├── email: string
│   ├── name: string
│   ├── preferences: map
│   │   ├── theme: string
│   │   ├── currency: string
│   │   └── locale: string
│   └── addresses: array
├── orders/{orderId}
│   ├── userId: string
│   ├── items: array<{productId, quantity, price}>
│   ├── total: number
│   ├── currency: string
│   ├── status: string
│   └── createdAt: timestamp
└── reviews/{reviewId}
    ├── productId: string
    ├── userId: string
    ├── rating: number
    └── comment: string
```text


```text
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

7. تخطيط واجهة/تجربة المستخدم

(1) جرد الصفحات

الصفحة المسار المكونات الأساسية
الرئيسية / SliverAppBar + GridView + CategoryChips
البحث /search SearchBar + FilterSheet + ProductList
تفاصيل المنتج /product/:id Hero + SliverAppBar + BottomSheet
السلة /cart CartList + QuantitySelector + TotalBar
الدفع /checkout AddressForm + PaymentSelector + OrderSummary
تأكيد الطلب /order/:id OrderTimeline + TrackingMap
الملف الشخصي /profile UserCard + OrderHistory + Settings
تسجيل الدخول /login EmailForm + GoogleButton + AppleButton

(2) رموز التصميم

DART
import 'package:flutter/material.dart';

// Design tokens: unified color/spacing/radius/font constants, carried by ThemeExtension

class ShopDesignTokens {
  // Colors
  static const primary = Color(0xFF0066CC);
  static const secondary = Color(0xFFFF6B35);
  static const sale = Color(0xFF4CAF50);
  static const discount = Color(0xFFEF5350);

  // Spacing
  static const xs = 4.0;
  static const sm = 8.0;
  static const md = 16.0;
  static const lg = 24.0;
  static const xl = 32.0;

  // Radius
  static const cardRadius = 12.0;
  static const buttonRadius = 8.0;
  static const inputRadius = 8.0;

  // Typography
  static const headlineSize = 24.0;
  static const titleSize = 18.0;
  static const bodySize = 14.0;
  static const captionSize = 12.0;
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

8. مثال كامل: هيكل دليل المشروع

TEXT
shop_app/
├── lib/
│   ├── main.dart
│   ├── app.dart
│   ├── core/
│   │   ├── router/
│   │   │   └── app_router.dart
│   │   ├── network/
│   │   │   ├── dio_client.dart
│   │   │   └── api_exception.dart
│   │   ├── storage/
│   │   │   ├── secure_storage.dart
│   │   │   ├── hive_cache.dart
│   │   │   └── shared_prefs.dart
│   │   ├── theme/
│   │   │   ├── app_theme.dart
│   │   │   └── brand_tokens.dart
│   │   └── platform/
│   │       └── platform_helper.dart
│   ├── domain/
│   │   ├── entities/
│   │   │   ├── product.dart
│   │   │   ├── cart_item.dart
│   │   │   ├── order.dart
│   │   │   └── user.dart
│   │   └── repositories/
│   │       ├── product_repository.dart
│   │       ├── auth_repository.dart
│   │       └── order_repository.dart
│   ├── data/
│   │   ├── repositories/
│   │   │   ├── product_repository_impl.dart
│   │   │   ├── auth_repository_impl.dart
│   │   │   └── order_repository_impl.dart
│   │   ├── datasources/
│   │   │   ├── firestore_datasource.dart
│   │   │   └── hive_datasource.dart
│   │   └── models/
│   │       ├── product_dto.dart
│   │       └── order_dto.dart
│   ├── application/
│   │   ├── notifiers/
│   │   │   ├── auth_notifier.dart
│   │   │   ├── product_notifier.dart
│   │   │   └── cart_notifier.dart
│   │   └── providers/
│   │       └── infrastructure_providers.dart
│   ├── presentation/
│   │   ├── screens/
│   │   │   ├── home_page.dart
│   │   │   ├── detail_page.dart
│   │   │   ├── cart_page.dart
│   │   │   ├── checkout_page.dart
│   │   │   └── login_page.dart
│   │   └── widgets/
│   │       ├── product_card.dart
│   │       ├── quantity_selector.dart
│   │       ├── cart_badge.dart
│   │       └── price_tag.dart
│   └── l10n/
│       ├── app_en.arb
│       ├── app_zh.arb
│       └── app_ja.arb
├── test/
│   ├── domain/
│   ├── application/
│   └── presentation/
├── integration_test/
├── android/
├── ios/
├── web/
├── windows/
├── macos/
├── pubspec.yaml
├── l10n.yaml
└── .github/workflows/
```text


```text
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

❓ أسئلة شائعة

س هل أحتاج كل طبقة من البنية النظيفة؟
ج للمشاريع الصغيرة-المتوسطة، يمكن دمج طبقتي التطبيق والنطاق في طبقة تطبيق واحدة. طبقة البيانات مطلوبة دائمًا (تجريد مصدر البيانات).
س ما الفرق بين Entity وDTO؟
ج Entity هو مفهوم نطاق (يُستخدم في منطق الأعمال)؛ DTO هو كائن نقل بيانات (يُستخدم لواجهة API/قاعدة البيانات). الكيانات لا تعتمد على عناصر خارجية؛ DTOs قد تتضمن تعليقات تسلسل JSON.
س هل من المفيد وضع واجهات Repository في طبقة النطاق؟
ج نعم. هذا مبدأ انقلاب التبعية: النطاق يحدد الواجهة، طبقة البيانات تنفذها. هكذا لا تعتمد طبقة النطاق على مكتبات خارجية مثل Firebase/Dio.
س كم يجب أن تستغرق مرحلة تصميم المشروع؟
ج مشاريع صغيرة-متوسطة: 1-2 أسبوع. مشاريع كبيرة: 2-4 أسابيع. استثمار 20% من الوقت في التصميم يوفر 50% من وقت التطوير.
س كيف أصمم مخطط Firestore بشكل مثالي؟
ج 1) صمم المستندات بعلاقات 1:1 أو 1:N؛ 2) تجنب التعشيش العميق؛ 3) ألغِ تطبيع البيانات المقروءة بكثرة (خزّن الأسماء بشكل زائد)؛ 4) طابق البيانات المكتوبة بكثرة.
س كيف أضمن اتباع تصميم البنية فعليًا؟
ج 1) استخدم قواعد lint لفرض اتجاه التبعية؛ 2) مراجعة الكود تتحقق من امتثال الطبقات؛ 3) اختبارات الوحدة تغطي حدود كل طبقة.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): اكتب 10 قصص مستخدم لـ ShopApp، مصنفة حسب أولوية P0/P1/P2.
  2. متوسط (الصعوبة ⭐⭐): صمم هيكل دليل من أربع طبقات بالبنية النظيفة، مع تعريف كيانات Product/Cart/Order وواجهات Repository المقابلة.
  3. متقدم (الصعوبة ⭐⭐⭐): أكمل وثيقة تصميم مشروع كاملة: قصص مستخدم + جدول مقارنة الاختيار التقني + مخطط طبقات البنية النظيفة + مخطط Firestore + جرد الصفحات + رموز التصميم.

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

(2) مثال: رسم بياني لتبعيات وحدات ShopApp

DART
final Map<String, List<String>> moduleDeps = {
  'core': [],
  'cart': ['core'],
  'checkout': ['core', 'cart'],
  'profile': ['core'],
  'catalog': ['core'],
};

الإخراج:

TEXT
core: 0 direct dependencies
cart: depends on core
checkout: depends on core, cart
profile: depends on core
catalog: depends on core

(3) مثال: جدول المسارات

DART
final Map<String, String> routes = {
  '/': 'CatalogPage',
  '/cart': 'CartPage',
  '/checkout': 'CheckoutPage',
  '/profile': 'ProfilePage',
};

الإخراج:

TEXT
/         -> CatalogPage
/cart     -> CartPage
/checkout -> CheckoutPage
/profile  -> ProfilePage
Web-Tutorial.com

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

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

100%