การจัดการการพึ่งพาระหว่างโมดูล
รักษากราฟให้สะอาดและไม่มีวงจร
การจัดการการพึ่งพาระหว่างโมดูล เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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รวมศูนย์รุ่น: แค็ตตาล็อกรุ่น
เมื่อมีหลายโมดูล คุณคงไม่ต้องการระบุรุ่นของไลบรารีซ้ำไปทั่ว 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" }ใช้แค็ตตาล็อกในโมดูล
จากนั้นโมดูลจะอ้างอิงไลบรารีผ่านตัวเข้าถึง 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แก้ไขวงจร
หากต้องการแก้ไขวงจร ให้แยกส่วนที่ใช้ร่วมกันออกมาเป็น โมดูลระดับล่างกว่า ซึ่งทั้งสองโมดูลสามารถขึ้นต่อได้ หากฟีเจอร์สองฟีเจอร์ต้องใช้ข้อมูลของกันและกัน สัญญาร่วมกันนั้นควรอยู่ในแกนกลาง ไม่ใช่อยู่ในฟีเจอร์ใดฟีเจอร์หนึ่ง
วิธีนี้จะทำให้การไหลลงด้านล่างกลับคืนมา ฟีเจอร์ทั้งสองจะชี้ลงไปยังแกนกลาง และแกนกลางจะไม่ชี้กลับขึ้นด้านบน
// 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 พิมพ์หรือแสดงผลกราฟของโมดูล และยังเพิ่มการทดสอบที่ ทำให้การสร้างล้มเหลว หากพบการขึ้นต่อที่ไม่อนุญาต (เช่น โมดูลแกนกลางขึ้นต่อกับฟีเจอร์) เครื่องมืออย่างปลั๊กอิน 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เฉพาะกับชนิดข้อมูลที่อยู่ในส่วนติดต่อสาธารณะ - รวมศูนย์รุ่นไว้ใน แค็ตตาล็อกรุ่น (
libs.versions.toml) - อย่าสร้าง วงจร เด็ดขาด เพราะ Gradle จะปฏิเสธวงจรเหล่านี้ ให้แก้ด้วยการแยกโค้ดที่ใช้ร่วมกันลงด้านล่าง หรือผกผันการขึ้นต่อด้วยส่วนติดต่อ
ถัดไป คุณจะเชื่อมฟีเจอร์ต่าง ๆ เข้าหากันผ่านการนำทางโดยไม่ทำให้ฟีเจอร์ขึ้นต่อกัน
คำถามที่พบบ่อย
บทเรียน “การจัดการการพึ่งพาระหว่างโมดูล” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การจัดการการพึ่งพาระหว่างโมดูล” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การจัดการการพึ่งพาระหว่างโมดูล”
รักษากราฟให้สะอาดและไม่มีวงจร คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การจัดการการพึ่งพาระหว่างโมดูล” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม
ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เหตุใดจึงควรแยกเป็นโมดูล
- โมดูลฟีเจอร์และโมดูลแกนกลาง
- การจัดการการพึ่งพาระหว่างโมดูล
- การนำทางข้ามโมดูล