یک معماری که از پایه قوی باشد استفاده کردنش در یک اپلیکیشن برای مقیاس گذاری و انتظارات کاربر بسیار مهم است. در پروژه ای که روی آن کار میکردیم من وظیفه جایگزینی API ها با ساختار جدید و بهینه شده را بر عهده گرفتم. برای ادغام این نوع تغییر مجبور شدم که کل برنامه را از اول بازنویسی کنم.
چرا؟ از آنجا که این کد با مدل های داده ای پیچیده همراه بود. در این زمان، من نمی خواستم مرتباً مرتکب همان اشتباهات شوم. برای حل این مشکل، معماری clean به دادم رسید. این معماری در ابتدا کمی سخت است اما ممکن است بهترین گزینه برای یک برنامه بزرگ با بسیاری از ویژگی ها و رویکرد SOLID باشد. بیایید فقط با زیر سؤال بردن هر جنبه ای از معماری به تفکیک بخش های ساده تر بپردازیم.
چرا رویکرد پاک کننده(Cleaner)؟
1. جداسازی کد در لایه های مختلف با مسئولیت های اختصاص یافته، اصلاح آن را آسان تر می کند.
2. سطح بالای انتزاع(abstraction)
3. اتصال سست بین کد(Loose coupling)
4. تست کردن کد بدون دردسر است.
لایه ها چیست؟
لایه Domain: منطق اپلیکیشن را اجرا می کند که مستقل از هر لایه ای باشد و فقط یک بسته kotlin خالص و بدون وابستگی خاص به اندروید است.
لایه Data: با اجرای رابط در Domain، داده های مورد نیاز برنامه را به لایه Domain منتقل می کند.
لایه Presentation: شامل لایه Domain و Data است که منطق UI را اجرا می کند.
Domain Layer چیست؟
عمومی ترین لایه از این سه لایه خواهد بود. این لایه Presentation را با لایه Data متصل می کند. این لایه ای است که منطق کارهای مرتبط با اپلیکیشن در آن اجرا خواهد شد.
ساختار لایه Domain برنامه
Usecase
وظیفه اش این است که با اینتری ها ارتباط برقرار کند یک نوع کلاس با یک هدف تک و خاص را در نظر بگیرید که میتواند کار های اینتری ها رو انجام بدهد مثلا اینتری درخواست داده می کند یوزکیس اون داده رو فراهم میکند فقط باید دقت داشته باشیم تغییر کلاس نباید روی اینتری ها تاثیر بگذارد و ارتباط فقط یک طرفه از خود کلاس به طرف اینتری ها است.
class GetNewsUseCase(private val transformer: FlowableRxTransformer<NewsSourcesEntity>,
private val repositories: NewsRepository): BaseFlowableUseCase<NewsSourcesEntity>(transformer){
override fun createFlowable(data: Map<String, Any>?): Flowable<NewsSourcesEntity> {
return repositories.getNews()
}
fun getNews(): Flowable<NewsSourcesEntity>{
val data = HashMap<String, String>()
return single(data)
}
}
کد بالا ، Flowable را برمی گرداند که با توجه به observer مورد نیاز قابل تغییر است.
دو پارامتر وجود دارد یکی از آنها ترانسفورماتورها یا ObservableTransformer است که کنترل می کند کدام thread برای اجرای منطق باشد و دیگر پارامتر Repository را کنترل می کند.
Repositories ویژگی های مورد نیاز که توسط لایه Data پیاده سازی می شود را مشخص می کند.
Data layer چیست؟
این لایه مسئول تهیه داده های مورد نیاز اپلیکیشن است. لایه Data باید جوری داده ها را طراحی کند که توسط هر برنامه کاربردی بدون تغییر در منطق presentation قابل استفاده مجدد باشد.
ساختار لایه data برنامه
API اجرای شبکه از راه دور را فراهم می کند. هر کتابخانه شبکه ای می تواند در این نوع مانند Retrofit ، Volley و غیره ادغام شود.
به طور مشابه ، DB اجرای بانک اطلاعاتی محلی را ارائه می دهد.
class NewsRepositoryImpl(private val remote: NewsRemoteImpl,
private val cache: NewsCacheImpl) : NewsRepository {
override fun getLocalNews(): Flowable<NewsSourcesEntity> {
return cache.getNews()
}
override fun getRemoteNews(): Flowable<NewsSourcesEntity> {
return remote.getNews()
}
override fun getNews(): Flowable<NewsSourcesEntity> {
val updateNewsFlowable = remote.getNews()
return cache.getNews()
.mergeWith(updateNewsFlowable.doOnNext{
remoteNews -> cache.saveArticles(remoteNews)
})
}
}
در Repository، ما پیاده سازی local، remote یا هر نوع ارائه دهنده داده را انجام داده ایم و کلاس بالاتر NewsRepositoryImpl.kt یک interface در لایه Domain را پیاده سازی می کند. این به عنوان یک نقطه دسترسی به لایه Data عمل می کند.
لایه ارائه (Presentation)چیست؟
لایه Presentation اجرای UI برنامه را انجام می دهد. این لایه فقط حالت نمایشی دارد منطق اپلیکیشن به هیچ وجه در این لایه نباید پیاده سازی شود. این لایه داخلی از معماری هایی مانند MVC ، MVP ، MVVM ، MVI و غیره استفاده می کند. این لایه ای است که همه چیز را به هم وصل می کند.
ساختار لایه presentation برنامه
پوشه DI تمام وابستگی ها را در ابتدای برنامه مانند network,viewmodels,usecase و غیره تزریق می کند DI در android می تواند با dagger، kodein، koin یا فقط با استفاده از الگوی service locator انجام شود. این فقط به برنامه بستگی دارد در برنامه های پیچیده di بسیار مفید است. من koin را فقط به دلیل این که فهم و اجرای آن آسانتر از dagger است انتخاب کردم.
چرا از ViewModel ها استفاده می کنیم؟
داده های مرتبط با Ui را به روش آگاهی ازlifecycle ذخیره و مدیریت میکند. به عنوان مثال زمانی که صفحه گوشی را می چرخانیم داده ها رفرش نمیشوند.
class NewsViewModel(private val getNewsUseCase: GetNewsUseCase,
private val mapper: Mapper<NewsSourcesEntity, NewsSources>) : BaseViewModel() {
companion object {
private val TAG = "viewmodel"
}
var mNews = MutableLiveData<Data<NewsSources>>()
fun fetchNews() {
val disposable = getNewsUseCase.getNews()
.flatMap { mapper.Flowable(it) }
.subscribe({ response ->
Log.d(TAG, "On Next Called")
mNews.value = Data(responseType = Status.SUCCESSFUL, data = response)
}, { error ->
Log.d(TAG, "On Error Called")
mNews.value = Data(responseType = Status.ERROR, error = Error(error.message))
}, {
Log.d(TAG, "On Complete Called")
})
addDisposable(disposable)
}
fun getNewsLiveData() = mNews
}
بنابراین، ViewModel اطلاعات مربوط به تغییر پیکربندی را حفظ می کند. در MVP ، Presenter با interface خود را به view متصل می کند که test آن را دشوار می کند اما در ViewModel ، به دلیل وجود architecturl components از interface استفاده نمیشود.
Base View Model از CompositeDisposable برای افزودن همه observable ها و حذف همه آنها در OnCleared از lifecycle.component استفاده می کند.
data class Data<RequestData>(var responseType: Status, var data: RequestData? = null, var error: Error? = null)
enum class Status { SUCCESSFUL, ERROR, LOADING }
یک کلاس data wrapper بر روی LiveData به عنوان یک کلاس helper استفاده می شود تا از status درخواست ( در صورت شروع) ، success یا هرگونه error درباره داده ها را مطلع شود.
چگونه همه لایه ها به هم وصل می شوند؟
هر لایه entity های خاص خود را دارد که مختص آن بسته است. Mapper برای تبدیل واحدهای یک لایه به حالت دیگر استفاده می شود.
ما برای هر لایه entity های مختلفی داریم به گونه ای که لایه کاملاً مستقل باشد و فقط داده های مورد نیاز به لایه بعدی منتقل شود.
در پایان جریان برنامه را مشاهده می کنید.
آموزشمون به پایان رسید اگر سوالی داشتید میتونید در بخش نظرات بنویسد که بهشون پاسخ بدم.
کد های این آموزش را میتوانید از لینک زیر دانلود کنید
https://github.com/maysambabaei/KotlinCleanArchitecture
نتیجه:
معماری پایه همبستگی اپلیکیشن و نیاز های آینده را تعریف می کند انتخاب معماری مناسب بستگی به اپلیکیشن دارد, اما مناسب ترین معماری را قبل از استارت پروژه انتخاب کنید که بتواند مقیاس پذیر، قابل آزمایش باشد تا در آینده مجبور نشوید که کد های خود را باز نویسی کنید.
برای افزودن دیدگاه خود، نیاز است ابتدا وارد حساب کاربریتان شوید