0Pricing
Android Academy · درس

إدارة تبعيات الوحدات

حافظ على رسم التبعيات نظيفًا وغير دوري

إدارة تبعيات الوحدات درس مجاني في Android Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Android Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Android Academy 4 دروس في المجموع.

الرسم البياني للتبعيات

تعلن كل وحدة عن الوحدات الأخرى التي تعتمد عليها. وتشكل هذه الوحدات معًا رسمًا بيانيًا للتبعيات. ويكون الرسم البياني السليم DAG (رسمًا بيانيًا موجّهًا لا دوريًا): تشير التبعيات في اتجاه واحد ولا تكوّن حلقة مطلقًا.

ستتعلم في هذا الدرس كيفية الإعلان عن التبعيات بطريقة نظيفة، واختيار إعداد Gradle المناسب، ومشاركة الإصدارات، ومنع الحلقات.

الإعلان عن تبعية وحدة

تضيف تبعية إلى وحدة أخرى باستخدام project(":path:to:module") داخل كتلة dependencies. ويطابق المسار بنية المجلدات وما أعلنته في settings.gradle.kts.

// feature/profile/build.gradle.kts
dependencies {
    implementation(project(":core:data"))
    implementation(project(":core:designsystem"))
    implementation(project(":core:model"))
}

implementation مقابل api

يتحكم الإعداد الذي تختاره فيما يتسرّب إلى المستهلكين:

  • implementation: التبعية خاصة. ولا تستطيع الوحدات التي تعتمد عليك رؤيتها. وهذا هو الخيار الافتراضي.
  • api: تُعاد إتاحة التبعية (بشكل انتقالي). استخدمه فقط عندما تأتي الأنواع العامة لديك من تلك التبعية.

فضّل implementation في جميع الحالات تقريبًا — فهو يحسّن سرعة البناء، لأن تغيير تبعية مخفية لا يجبر الوحدات المستهلكة على إعادة الترجمة.

// core/data/build.gradle.kts
dependencies {
    // Repository signatures return :core:model types,
    // so consumers need to SEE it -> api
    api(project(":core:model"))

    // Network is an internal detail -> implementation
    implementation(project(":core:network"))
}

لماذا يسرّع implementation عمليات البناء

باستخدام implementation، يعرف Gradle أن تغيير تبعية مخفية لا يمكن أن يؤثر في ABI العام للوحدة. لذلك لا تحتاج الوحدات المستهلكة إلى إعادة الترجمة. أما مع api، فينتشر التغيير عبر كل مستهلك انتقالي.

قاعدة عامة: ضع التبعية في api فقط إذا ظهرت في الأنواع العامة للوحدة (أنواع الإرجاع أو المعاملات العامة). وإلا فاستخدم implementation.

// Public -> needs api
fun observeUser(): Flow<User>   // Flow and User leak out

// Internal -> implementation is enough
private val client: OkHttpClient // never exposed

مركزة الإصدارات: Version Catalog

عند وجود وحدات كثيرة، لن ترغب في تكرار إصدارات المكتبات في كل مكان. يعرّف version catalog في Gradle ‏(gradle/libs.versions.toml) الإصدارات والأسماء المستعارة مرة واحدة. ثم تشير كل وحدة إلى الاسم المستعار نفسه.

# gradle/libs.versions.toml
[versions]
compose-bom = "2024.09.00"
retrofit = "2.11.0"

[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

استخدام Catalog في وحدة

تشير الوحدات بعد ذلك إلى المكتبات من خلال accessor المُنشأ libs. ويعني عدم وجود أرقام إصدارات في ملف البناء أن الترقية تتم في مكان واحد.

// core/network/build.gradle.kts
dependencies {
    implementation(platform(libs.compose.bom))
    implementation(libs.retrofit)
}

الخطيئة الكبرى: الحلقات

تحدث الحلقة عندما تعتمد الوحدة A على B، ثم تعتمد B مجددًا على A (مباشرة أو عبر سلسلة). ويرفض Gradle البناء عند وجود تبعية دائرية. كما يشير ذلك إلى مشكلة في التصميم: فالحد الفاصل بين الوحدتين غير صحيح.

// :feature:cart  -> implementation(project(":feature:checkout"))
// :feature:checkout -> implementation(project(":feature:cart"))
//
// Gradle error:
// Circular dependency between the following tasks:
// :feature:cart:compile -> :feature:checkout:compile -> :feature:cart:compile

كسر حلقة

لكسر حلقة، استخرج الجزء المشترك إلى وحدة أدنى يمكن لكلتا الوحدتين الاعتماد عليها. إذا احتاجت ميزتان إلى بيانات بعضهما، فينبغي أن يكون هذا العقد المشترك في core، لا في أي من الميزتين.

يعيد ذلك التدفق向 الأسفل: تشير الميزتان كلتاهما إلى core في الأسفل، ولا تشير core مطلقًا إلى الأعلى.

// Before: cart <-> checkout (cycle)
// After:  cart -> :core:order  <-  checkout

// core/order/OrderContract.kt
data class Order(val items: List<CartItem>, val total: Double)

// feature/cart    -> implementation(project(":core:order"))
// feature/checkout-> implementation(project(":core:order"))

العكس: الاعتماد على التجريدات

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

// core/analytics defines the contract
interface AnalyticsLogger {
    fun log(event: String)
}

// :app provides the real implementation and injects it down
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
    @Provides
    fun logger(impl: FirebaseAnalyticsLogger): AnalyticsLogger = impl
}

تصوير الرسم البياني وحمايته

يمكنك أن تطلب من Gradle طباعة رسم الوحدات أو عرضه، بل ويمكنك أيضًا إضافة اختبار يفشل البناء إذا ظهرت تبعية محظورة (مثل اعتماد وحدة core على ميزة). وتُنشئ أدوات مثل المكوّن الإضافي module-graph مخططًا تلقائيًا.

# Print the project structure
./gradlew projects

# Inspect why :feature:home pulls in a library
./gradlew :feature:home:dependencies --configuration debugRuntimeClasspath

مثال نظيف لا يحتوي على حلقات

إليك رسمًا بيانيًا سليمًا. اقرأه من الأعلى إلى الأسفل؛ فلا يشير أي سهم إلى الأعلى، ولا تشير أي وحدتين إلى بعضهما. وهذا هو الهدف الذي تسعى إليه تمامًا.

// :app
//   -> :feature:home   -> :core:data -> :core:network -> :core:model
//   -> :feature:profile -> :core:data -> :core:database -> :core:model
//   -> :core:designsystem
//
// Every path ends at :core:model. No cycles. Builds in parallel.

تحقق سريع

تستخدم وحدة :core:network ‏OkHttpClient داخليًا فقط — ولا يظهر مطلقًا في أي توقيع لدالة عامة. ما إعداد Gradle الذي ينبغي استخدامه للإعلان عن تبعية OkHttp؟

مراجعة: إدارة تبعيات الوحدات

لقد تعلمت كيفية الحفاظ على صحة رسم الوحدات:

  • أعلن عن تبعيات الوحدات باستخدام project(":path").
  • استخدم implementation افتراضيًا؛ ولا تستخدم api إلا للأنواع الموجودة في الواجهة العامة.
  • مركِز الإصدارات في version catalog ‏(libs.versions.toml).
  • لا تنشئ حلقات مطلقًا — إذ يرفضها Gradle؛ اكسرها باستخراج الشيفرة المشتركة إلى الأسفل أو بعكس التبعية باستخدام الواجهات.

بعد ذلك، ستصل الميزات ببعضها من خلال التنقل دون ربطها بإحكام.

الأسئلة الشائعة

هل درس «إدارة تبعيات الوحدات» مجاني؟

نعم — نص درس «إدارة تبعيات الوحدات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Android Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Android Academy 4 دروس في المجموع.

ماذا ستتعلم في «إدارة تبعيات الوحدات»؟

حافظ على رسم التبعيات نظيفًا وغير دوري تتمرن على Android Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Android Academy؟

لا تُشترط خبرة سابقة. Android Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «إدارة تبعيات الوحدات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Android Academy هذا؟

نعم. كل درس في Android Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. لماذا نُجزّئ التطبيق إلى وحدات
  2. وحدات الميزات والوحدات الأساسية
  3. إدارة تبعيات الوحدات
  4. التنقّل بين الوحدات
← العودة إلى Android Academy