هیچ استراتژی واحدی برای مدولارسازی وجود ندارد که برای همه پروژه ها مناسب باشد. با توجه به ماهیت انعطاف پذیر Gradle، محدودیت های کمی در مورد نحوه سازماندهی یک پروژه وجود دارد. این صفحه مروری بر برخی از قوانین کلی و الگوهای رایجی که میتوانید هنگام توسعه برنامههای اندروید چند ماژول به کار ببرید، ارائه میکند.
انسجام بالا و اصل جفت کم
یکی از راههای مشخص کردن یک پایگاه کد مدولار، استفاده از ویژگیهای کوپلینگ و پیوستگی است. کوپلینگ میزان وابستگی ماژول ها به یکدیگر را اندازه گیری می کند. انسجام، در این زمینه، چگونگی ارتباط عملکردی عناصر یک ماژول را اندازهگیری میکند. به عنوان یک قانون کلی، شما باید برای اتصال کم و انسجام بالا تلاش کنید:
- اتصال کم به این معنی است که ماژول ها باید تا حد امکان مستقل از یکدیگر باشند، به طوری که تغییرات در یک ماژول تاثیر صفر یا حداقل بر سایر ماژول ها داشته باشد. ماژول ها نباید از عملکرد داخلی ماژول های دیگر آگاهی داشته باشند .
- انسجام بالا به این معنی است که ماژول ها باید مجموعه ای از کدها را تشکیل دهند که به عنوان یک سیستم عمل می کند. آنها باید مسئولیتهای مشخصی داشته باشند و در محدوده دانش حوزه خاصی بمانند. یک نمونه برنامه الکترونیکی را در نظر بگیرید. ممکن است نامناسب باشد که کد مربوط به کتاب و پرداخت را با هم در یک ماژول مخلوط کنید زیرا دو حوزه عملکردی متفاوت هستند.
انواع ماژول ها
نحوه سازماندهی ماژول های خود عمدتاً به معماری برنامه شما بستگی دارد. در زیر برخی از انواع متداول ماژولها وجود دارد که میتوانید با پیروی از معماری برنامه پیشنهادی ما، در برنامه خود معرفی کنید.
ماژول های داده
یک ماژول داده معمولاً حاوی یک مخزن، منابع داده و کلاسهای مدل است. سه مسئولیت اصلی یک ماژول داده عبارتند از:
- محصور کردن تمام داده ها و منطق تجاری یک دامنه خاص : هر ماژول داده باید مسئول رسیدگی به داده هایی باشد که نشان دهنده یک دامنه خاص است. میتواند انواع مختلفی از دادهها را تا زمانی که مرتبط باشند مدیریت کند.
- مخزن را به عنوان یک API خارجی در معرض نمایش قرار دهید : API عمومی یک ماژول داده باید یک مخزن باشد زیرا آنها مسئول نمایش داده ها در بقیه برنامه هستند.
- پنهان کردن تمام جزئیات پیاده سازی و منابع داده از خارج : منابع داده فقط باید توسط مخازن از همان ماژول قابل دسترسی باشند. آنها به بیرون پنهان می مانند. شما می توانید این را با استفاده از کلمه کلیدی
private
یا نمایinternal
Kotlin اعمال کنید.
ماژول های ویژگی
یک ویژگی بخشی جدا شده از عملکرد یک برنامه است که معمولاً مربوط به یک صفحه یا مجموعه ای از صفحه های نزدیک مرتبط است، مانند ثبت نام یا جریان پرداخت. اگر برنامه شما دارای ناوبری نوار پایینی است، به احتمال زیاد هر مقصد یک ویژگی است.
ویژگی ها با صفحه نمایش یا مقصد در برنامه شما مرتبط هستند. بنابراین، آنها احتمالاً یک رابط کاربری و ViewModel
مرتبط دارند تا منطق و حالت خود را مدیریت کنند . لازم نیست یک ویژگی واحد به یک نما یا مقصد ناوبری محدود شود. ماژول های ویژگی به ماژول های داده بستگی دارند.
ماژول های برنامه
ماژول های برنامه یک نقطه ورود به برنامه هستند. آنها به ماژول های ویژگی بستگی دارند و معمولاً ناوبری ریشه را ارائه می دهند. به لطف انواع ساخت، یک ماژول برنامه واحد را می توان به تعدادی باینری مختلف کامپایل کرد.
اگر برنامه شما چندین نوع دستگاه مانند خودکار، لباس یا تلویزیون را هدف قرار می دهد، برای هر کدام یک ماژول برنامه تعریف کنید. این به جداسازی وابستگی های خاص پلت فرم کمک می کند.
ماژول های رایج
ماژول های رایج که به عنوان ماژول های هسته نیز شناخته می شوند، حاوی کدهایی هستند که ماژول های دیگر اغلب از آن استفاده می کنند. آنها افزونگی را کاهش می دهند و هیچ لایه خاصی را در معماری برنامه نشان نمی دهند. در زیر نمونه هایی از ماژول های رایج آورده شده است:
- ماژول رابط کاربری : اگر از عناصر رابط کاربری سفارشی یا نام تجاری پیچیده در برنامه خود استفاده می کنید، باید مجموعه ویجت خود را در یک ماژول کپسوله کنید تا همه ویژگی ها دوباره استفاده شوند. این می تواند به سازگاری رابط کاربری شما در ویژگی های مختلف کمک کند. به عنوان مثال، اگر قالببندی شما متمرکز باشد، میتوانید از یک بازسازی دردناک در هنگام تغییر برند اجتناب کنید.
- ماژول تجزیه و تحلیل : ردیابی اغلب توسط الزامات تجاری و با توجه کمی به معماری نرم افزار دیکته می شود. ردیاب های تجزیه و تحلیل اغلب در بسیاری از مؤلفه های نامرتبط استفاده می شود. اگر برای شما اینطور است، ممکن است ایده خوبی باشد که یک ماژول تجزیه و تحلیل اختصاصی داشته باشید.
- ماژول شبکه : هنگامی که بسیاری از ماژول ها به اتصال شبکه نیاز دارند، ممکن است ماژول اختصاصی برای ارائه یک سرویس گیرنده http داشته باشید. به خصوص زمانی مفید است که مشتری شما نیاز به پیکربندی سفارشی داشته باشد.
- ماژول Utility : ابزارهای کمکی که به عنوان کمک کننده نیز شناخته می شوند، معمولاً قطعات کوچکی از کد هستند که مجدداً در سراسر برنامه استفاده می شوند. نمونه هایی از ابزارهای کمکی شامل کمک های آزمایشی، یک تابع قالب بندی ارز، اعتبارسنجی ایمیل یا یک اپراتور سفارشی است.
ماژول های تست
ماژول های تست ، ماژول های اندرویدی هستند که فقط برای اهداف آزمایشی استفاده می شوند. ماژول ها حاوی کد تست، منابع تست و وابستگی های تست هستند که فقط برای اجرای تست ها مورد نیاز هستند و در طول زمان اجرای برنامه مورد نیاز نیستند. ماژولهای تست برای جدا کردن کدهای مخصوص آزمون از برنامه اصلی ایجاد میشوند و مدیریت و نگهداری کد ماژول را آسانتر میکنند.
از موارد برای ماژول های تست استفاده کنید
مثالهای زیر موقعیتهایی را نشان میدهند که پیادهسازی ماژولهای تست میتواند به ویژه مفید باشد:
کد تست مشترک : اگر چندین ماژول در پروژه خود دارید و برخی از کدهای تست برای بیش از یک ماژول قابل اجرا هستند، می توانید یک ماژول تست برای اشتراک گذاری کد ایجاد کنید. این می تواند به کاهش تکرار و حفظ کد تست شما کمک کند. کد آزمایش مشترک میتواند شامل کلاسها یا توابع کاربردی، مانند ادعاهای سفارشی یا تطبیقها، و همچنین دادههای آزمایشی، مانند پاسخهای JSON شبیهسازی شده باشد.
تنظیمات ساخت پاک کننده : ماژول های تست به شما امکان می دهند پیکربندی های ساخت تمیزتری داشته باشید، زیرا می توانند فایل
build.gradle
خود را داشته باشند. لازم نیست فایلbuild.gradle
ماژول برنامه خود را با پیکربندی هایی که فقط مربوط به تست ها هستند، شلوغ کنید.تستهای یکپارچهسازی : ماژولهای آزمایشی را میتوان برای ذخیره تستهای یکپارچهسازی استفاده کرد که برای آزمایش تعاملات بین بخشهای مختلف برنامه شما، از جمله رابط کاربری، منطق تجاری، درخواستهای شبکه، و جستارهای پایگاه داده استفاده میشوند.
برنامه های کاربردی در مقیاس بزرگ : ماژول های آزمایشی به ویژه برای برنامه های کاربردی در مقیاس بزرگ با پایگاه های کد پیچیده و چندین ماژول مفید هستند. در چنین مواردی، ماژول های تست می توانند به بهبود سازماندهی کد و قابلیت نگهداری کمک کنند.
ارتباط ماژول به ماژول
ماژول ها به ندرت در جدایی کامل وجود دارند و اغلب به ماژول های دیگر متکی هستند و با آنها ارتباط برقرار می کنند. حتی زمانی که ماژولها با هم کار میکنند و مرتباً اطلاعات را رد و بدل میکنند، مهم است که کوپلینگ را پایین نگه دارید. گاهی اوقات ارتباط مستقیم بین دو ماژول یا مانند محدودیت های معماری مطلوب نیست. همچنین ممکن است غیرممکن باشد، مانند وابستگی های چرخه ای.
برای غلبه بر این مشکل می توانید ماژول سومی را بین دو ماژول دیگر واسطه کنید. ماژول واسطه میتواند پیامهای هر دو ماژول را گوش کند و در صورت نیاز آنها را ارسال کند. در برنامه نمونه ما، صفحه پرداخت باید بداند که کدام کتاب را بخرد، حتی اگر رویداد در یک صفحه جداگانه که بخشی از یک ویژگی متفاوت است، آغاز شده است. در این مورد، میانجی ماژولی است که دارای نمودار ناوبری (معمولاً یک ماژول برنامه) است. در مثال، ما از ناوبری برای انتقال دادهها از ویژگی خانه به ویژگی پرداخت با استفاده از مؤلفه Navigation استفاده میکنیم.
navController.navigate("checkout/$bookId")
مقصد تسویه حساب یک شناسه کتاب را به عنوان آرگومان دریافت می کند که از آن برای واکشی اطلاعات درباره کتاب استفاده می کند. میتوانید از دسته حالت ذخیرهشده برای بازیابی آرگومانهای پیمایش در ViewModel
ویژگی مقصد استفاده کنید.
class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {
val uiState: StateFlow<CheckoutUiState> =
savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
// produce UI state calling bookRepository.getBook(bookId)
}
…
}
شما نباید اشیا را به عنوان آرگومان های ناوبری ارسال کنید. در عوض، از شناسههای ساده استفاده کنید که ویژگیها میتوانند از آنها برای دسترسی و بارگذاری منابع مورد نظر از لایه داده استفاده کنند. به این ترتیب، اتصال را پایین نگه می دارید و اصل منبع حقیقت را نقض نمی کنید.
در مثال زیر، هر دو ماژول ویژگی به یک ماژول داده وابسته هستند. این امکان به حداقل رساندن مقدار دادههایی را که ماژول واسطه نیاز به ارسال دارد و کوپلینگ بین ماژولها را کم نگه میدارد. ماژول ها به جای ارسال اشیا، باید شناسه های اولیه را مبادله کنند و منابع را از یک ماژول داده مشترک بارگیری کنند.
وارونگی وابستگی
وارونگی وابستگی زمانی است که کد خود را طوری سازماندهی می کنید که انتزاع از یک پیاده سازی مشخص جدا باشد.
- Abstraction : قراردادی که نحوه تعامل اجزا یا ماژول های برنامه شما با یکدیگر را مشخص می کند. ماژول های انتزاعی API سیستم شما را تعریف می کنند و شامل رابط ها و مدل ها هستند.
- پیاده سازی بتن : ماژول هایی که به ماژول انتزاعی وابسته هستند و رفتار یک انتزاع را پیاده سازی می کنند.
ماژول هایی که بر رفتار تعریف شده در ماژول انتزاعی تکیه دارند، باید فقط به خود انتزاع بستگی داشته باشند، نه پیاده سازی های خاص.
مثال
یک ماژول ویژگی را تصور کنید که برای کار کردن به یک پایگاه داده نیاز دارد. ماژول ویژگی به نحوه پیاده سازی پایگاه داده مربوط نمی شود، خواه یک پایگاه داده اتاق محلی باشد یا یک نمونه Firestore راه دور. فقط نیاز به ذخیره و خواندن داده های برنامه دارد.
برای دستیابی به این هدف، ماژول ویژگی به ماژول انتزاعی بستگی دارد تا اجرای یک پایگاه داده خاص. این انتزاع API پایگاه داده برنامه را تعریف می کند. به عبارت دیگر، قوانینی را برای نحوه تعامل با پایگاه داده تعیین می کند. این به ماژول ویژگی اجازه می دهد تا از هر پایگاه داده ای بدون نیاز به دانستن جزئیات پیاده سازی اساسی آن استفاده کند.
ماژول پیاده سازی بتن اجرای واقعی API های تعریف شده در ماژول انتزاعی را فراهم می کند. برای انجام این کار، ماژول پیاده سازی نیز به ماژول انتزاعی بستگی دارد.
تزریق وابستگی
در حال حاضر ممکن است تعجب کنید که چگونه ماژول ویژگی با ماژول پیاده سازی مرتبط است. پاسخ تزریق وابستگی است. ماژول ویژگی مستقیماً نمونه پایگاه داده مورد نیاز را ایجاد نمی کند. در عوض، مشخص می کند که به چه وابستگی هایی نیاز دارد. سپس این وابستگی ها به صورت خارجی، معمولاً در ماژول برنامه ، عرضه می شوند.
releaseImplementation(project(":database:impl:firestore"))
debugImplementation(project(":database:impl:room"))
androidTestImplementation(project(":database:impl:mock"))
مزایا
مزایای جداسازی APIها و پیاده سازی آنها به شرح زیر است:
- قابلیت تعویض : با جداسازی واضح API و ماژول های پیاده سازی، می توانید چندین پیاده سازی را برای یک API یکسان توسعه دهید و بدون تغییر کدی که از API استفاده می کند، بین آنها جابجا شوید. این می تواند به ویژه در سناریوهایی که می خواهید قابلیت ها یا رفتارهای متفاوتی را در زمینه های مختلف ارائه دهید مفید باشد. به عنوان مثال، یک پیاده سازی ساختگی برای آزمایش در مقابل یک پیاده سازی واقعی برای تولید.
- جداسازی : جداسازی به این معنی است که ماژول هایی که از انتزاعات استفاده می کنند به هیچ فناوری خاصی وابسته نیستند. اگر تصمیم بگیرید که بعداً پایگاه داده خود را از Room به Firestore تغییر دهید، آسان تر خواهد بود زیرا تغییرات فقط در ماژول خاصی که کار را انجام می دهد (ماژول پیاده سازی) اتفاق می افتد و روی ماژول های دیگر که از API پایگاه داده شما استفاده می کنند تأثیر نمی گذارد.
- آزمایش پذیری : جداسازی APIها از پیاده سازی آنها می تواند آزمایش را تا حد زیادی تسهیل کند. می توانید موارد آزمایشی را در برابر قراردادهای API بنویسید. شما همچنین می توانید از پیاده سازی های مختلف برای آزمایش سناریوها و موارد لبه مختلف از جمله پیاده سازی های ساختگی استفاده کنید.
- بهبود عملکرد ساخت : هنگامی که یک API و پیاده سازی آن را به ماژول های مختلف جدا می کنید، تغییرات در ماژول پیاده سازی، سیستم ساخت را مجبور نمی کند که ماژول ها را بسته به ماژول API دوباره کامپایل کند. این منجر به زمان ساخت سریع تر و افزایش بهره وری می شود، به ویژه در پروژه های بزرگ که زمان ساخت می تواند قابل توجه باشد.
کی جدا بشه
در موارد زیر جدا کردن APIها از پیاده سازی آنها مفید است:
- قابلیتهای متنوع : اگر بتوانید بخشهایی از سیستم خود را به روشهای مختلف پیادهسازی کنید، یک API واضح امکان تعویض پیادهسازیهای مختلف را فراهم میکند. برای مثال، ممکن است یک سیستم رندر داشته باشید که از OpenGL یا Vulkan استفاده می کند، یا یک سیستم صورتحساب که با Play یا API صورتحساب داخلی شما کار می کند.
- برنامه های کاربردی چندگانه : اگر در حال توسعه چندین برنامه با قابلیت های مشترک برای پلتفرم های مختلف هستید، می توانید API های مشترکی را تعریف کنید و پیاده سازی های خاصی را در هر پلتفرم توسعه دهید.
- تیمهای مستقل : این جداسازی به توسعهدهندگان یا تیمهای مختلف اجازه میدهد تا به طور همزمان روی بخشهای مختلف پایگاه کد کار کنند. توسعه دهندگان باید بر درک قراردادهای API و استفاده صحیح از آنها تمرکز کنند. آنها نیازی به نگرانی در مورد جزئیات پیاده سازی ماژول های دیگر ندارند.
- پایگاه کد بزرگ : هنگامی که پایگاه کد بزرگ یا پیچیده است، جدا کردن API از پیاده سازی، کد را قابل مدیریت تر می کند. این به شما امکان میدهد پایگاه کد را به واحدهای دانهدار، قابل درک و قابل نگهداری تقسیم کنید.
چگونه پیاده سازی کنیم؟
برای پیاده سازی وارونگی وابستگی، مراحل زیر را دنبال کنید:
- ایجاد یک ماژول انتزاعی : این ماژول باید حاوی API ها (رابط ها و مدل ها) باشد که رفتار ویژگی شما را مشخص می کند.
- ایجاد ماژول های پیاده سازی : ماژول های پیاده سازی باید بر ماژول API تکیه کنند و رفتار یک انتزاع را پیاده سازی کنند.
- ماژول های سطح بالا را به ماژول های انتزاعی وابسته کنید : به جای اینکه مستقیماً به یک پیاده سازی خاص وابسته باشید، ماژول های خود را به ماژول های انتزاعی وابسته کنید. ماژول های سطح بالا نیازی به دانستن جزئیات پیاده سازی ندارند، آنها فقط به قرارداد (API) نیاز دارند.
- ارائه ماژول پیاده سازی : در نهایت، شما باید پیاده سازی واقعی را برای وابستگی های خود ارائه دهید. پیاده سازی خاص به تنظیمات پروژه شما بستگی دارد، اما ماژول برنامه معمولا مکان خوبی برای انجام این کار است. برای ارائه پیاده سازی، آن را به عنوان یک وابستگی برای نوع ساخت انتخابی خود یا مجموعه منبع آزمایشی مشخص کنید.
بهترین شیوه های عمومی
همانطور که در ابتدا ذکر شد هیچ راه درستی برای توسعه یک برنامه چند ماژول وجود ندارد. درست مانند بسیاری از معماریهای نرمافزاری، راههای متعددی برای ماژولار کردن یک برنامه وجود دارد. با این وجود، توصیههای کلی زیر میتواند به شما کمک کند کد خود را خوانا، قابل نگهداری و آزمایشپذیرتر کنید.
پیکربندی خود را ثابت نگه دارید
هر ماژول سربار پیکربندی را معرفی می کند. اگر تعداد ماژولهای شما به حد معینی برسد، مدیریت پیکربندی ثابت به یک چالش تبدیل میشود. به عنوان مثال، مهم است که ماژول ها از وابستگی های همان نسخه استفاده کنند. اگر شما نیاز به به روز رسانی تعداد زیادی ماژول را دارید تا فقط یک نسخه وابستگی ایجاد کنید، این نه تنها یک تلاش است، بلکه اتاقی برای اشتباهات احتمالی است. برای حل این مشکل، می توانید از یکی از ابزارهای gradle برای متمرکز کردن پیکربندی خود استفاده کنید:
- کاتالوگ های نسخه یک نوع لیست امن از وابستگی ها هستند که توسط Gradle در حین همگام سازی ایجاد می شوند. این یک مکان مرکزی برای اعلام همه وابستگیهای شما است و برای همه ماژولهای یک پروژه در دسترس است.
- از پلاگین های کنوانسیون برای به اشتراک گذاشتن منطق ساخت بین ماژول ها استفاده کنید.
تا حد امکان کمتر در معرض دید قرار دهید
رابط عمومی یک ماژول باید حداقل باشد و فقط موارد ضروری را در معرض دید قرار دهد. نباید هیچ گونه جزئیات اجرایی در خارج از آن افشا شود. همه چیز را تا کمترین حد ممکن در نظر بگیرید. از دامنه دید private
یا internal
کاتلین برای خصوصی کردن ماژول اعلامیه ها استفاده کنید. هنگام اعلام وابستگی ها در ماژول خود، implementation
به api
ترجیح دهید. دومی وابستگی های گذرا را در اختیار مصرف کنندگان ماژول شما قرار می دهد. استفاده از پیاده سازی ممکن است زمان ساخت را بهبود بخشد زیرا تعداد ماژول هایی را که نیاز به بازسازی دارند کاهش می دهد.
ماژول های Kotlin و Java را ترجیح دهید
سه نوع ماژول ضروری وجود دارد که Android Studio از آنها پشتیبانی می کند:
- ماژول های برنامه یک نقطه ورود به برنامه شما هستند. آنها می توانند حاوی کد منبع، منابع، دارایی ها و یک
AndroidManifest.xml
باشند. خروجی یک ماژول برنامه یک بسته نرم افزاری Android (AAB) یا یک بسته برنامه کاربردی Android (APK) است. - ماژول های کتابخانه دارای محتوایی مشابه ماژول های برنامه هستند. آنها توسط سایر ماژول های اندروید به عنوان یک وابستگی استفاده می شوند. خروجی یک ماژول کتابخانه یک آرشیو Android (AAR) است که از نظر ساختاری مشابه ماژولهای برنامه هستند، اما آنها در یک فایل بایگانی Android (AAR) کامپایل میشوند که بعداً میتواند توسط ماژولهای دیگر به عنوان یک وابستگی استفاده شود. ماژول کتابخانه ای امکان کپسوله کردن و استفاده مجدد از منطق و منابع مشابه را در بسیاری از ماژول های برنامه فراهم می کند.
- کتابخانههای Kotlin و Java هیچ منبع، دارایی یا فایل مانیفست اندرویدی ندارند.
از آنجایی که ماژولهای اندروید دارای سربار هستند، ترجیحاً میخواهید تا حد امکان از نوع Kotlin یا Java استفاده کنید.