آموزش الگوی طراحی repository


انتخاب الگوی طراحی repository

انتخاب معماری مناسب با پروژه دغدغه همیشگی برنامه نویس هاست. متاسفانه هیچ کدام از معماری های موجود پاسخگوی تمام نیازهای برنامه نیستند. راه حل این مشکل توجه به این مورد است که قصد مدیریت کدام قسمت از پروژه را دارید. خود این پاسخ وابسته به موارد متعددی ست.

وقتی این مورد repository یا منبع داده ای باشد که برنامه شما از آن تغذیه می کند، راهکارهای مفیدی برای نوشتن کد تمیز و قابل ارجاع وجود دارد. ممکن است لازم باشد منطق برنامه و دسترسی به داده ها در منطق جداگانه ای طرح شود. ایجاد چنین بخشی در برنامه در واقع پیاده سازی الگوی طراحی repositoryست.

به این ترتیب

  • معماری منعطف و قابل تستی برای این قسمت از برنامه ساخته می شود.
  • اگر نیاز به ایجاد تغییری در منطق دسترسی به داده ها باشد، نیازی به تغییر مستقیم repository داده ها نیست.
  • منطق کار با داده ها در نقطه ای جدا از روند برنامه متمرکز شده است.

در این مقاله به بررسی ساخت الگوی طراحی repository در برنامه می پردازیم.

آشنایی با طراحی الگوی repository

الگوی repository روشی ست که در آن viewModel یا کلاس presenter لازم نیست، نحوه تزریق داده ها به برنامه را مدیریت کنند. تنها وظیفه ای که در این بخش ها مدیریت می شود نحوه ایجاد request و کار با پاسخ دریافتی ست.

یک مثال بد

در این مثال به برنامه ای توجه می کنیم که در آن الگوی repository پیاده نشده. در این مثال قصد fetch یا واکشی داده ها را از سرور به کمک retrofit داریم.

class DetailActivityViewModel(
    private val pokemonAPI: PokemonAPI
) : BaseObservableViewModel() {
    ...

    init {
        job = CoroutineScope(dispatcherProvider.IO).launch {
            ...

            val pokemon = pokemonAPI.getPokemonDetailAsync(pokemonName).await()

            ...
        }
    }

    ...
}

هر چند که این مثال واقعا افتضاح نیست اما یک محدودیت مهم را نشان می دهد. اگر بنا بود داده ها را از GraphQL api یا یک دیتابیس محلی یا ترکیبی از هر دو دریافت کنیم چطور؟

بستگی داشت کدام مورد نیاز برنامه شما باشد و نوشتن کد جداگانه برای هر حالت کار تمیزی نیست. ابتدا باید خصوصیات و فیلدهای مورد نیاز در viewModel تعریف کرد. در این صورت viewModel با کدهای مربوط به کار با لایه ی داده ها اشباع می شود. کدهایی که همیشه به آنها نیاز ندارد اما در تمام دوره زندگی خود آنها را حمل می کند.

ساخت یک اینترفیس برای repository

همان طور که گفته شد viewModel برنامه نیازی نیست از نحوه ورود داده ها به برنامه آگاه باشد. بنابراین خصوصیات ضروری برای viewModel را در یک interface تعریف می کنیم.

interface PokemonRepository {
    suspend fun getPokemon(): PokemonResponse
    suspend fun getPokemonDetail(pokemonName: String): Pokemon
}

بعد از تعریف این قسمت به viewModel رفته و آن را برای استفاده از این interface به روز می نماییم.

class DetailActivityViewModel(
    private val repository: PokemonRepository
) : BaseObservableViewModel() {
    ...

    init {
        job = CoroutineScope(dispatcherProvider.IO).launch {
            ...

            val pokemon = repository.getPokemonDetail(pokemonName)

            ...
        }
    }

    ...
}

حالا از نظر کنترل نحوه ورود داده ها به برنامه به قسمت خوبی رسیدیم. دریافت و به روز رسانی داده ها از منبع بدون درگیری viewModel به خوبی انجام می گردد.

پیاده سازی

این تغییرات برای ایجاد کلاسی ست که در دو حالت مختلف از منبع داده دریافت می کند.

open class PokemonRetrofitService(
    private val api: PokemonAPI
): PokemonRepository {
    override suspend fun getPokemon(): PokemonResponse {
        return api.getPokemonAsync().await()
    }

    override suspend fun getPokemonDetail(pokemonName: String): Pokemon {
        return api.getPokemonDetailAsync(pokemonName).await()
    }
}

حالا نحوه ایجاد request را برای ساخت viewModel مانند قطعه کد زیر به روز رسانی می کنیم:

private val viewModelFactory = object : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        val pokemonAPI = ...
        val repository = PokemonRetrofitService(pokemonAPI)

        return DetailActivityViewModel(repository) as T
    }
}

مثالی برای پیاده سازی منابع چندگانه دریافت داده

وقتی نیاز به پیاده سازی چندین حالت برای واکشی داده ها در برنامه باشد این نوع پیاده سازی کارآمد است. رایج ترین مثال این حالت هنگامی ست که برنامه هم نیاز به دریافت داده ها از سرور و هم دیتابیس محلی دارد.

برنامه ای را در نظر بگیرید که در حالت offline هم داده های دریافتی از سرور را نمایش می دهد. اگر برنامه شما با الگوی طراحی repository پیاده شده باشد دیگر نگران به روز رسانی viewModel برای چنین تغییری نیستیم.

تنها لازم است DatabasePokemonService  را بسازیم و آن را به viewModel پاس دهیم.

private val viewModelFactory = object : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        val repository = getPokemonRepository()

        return DetailActivityViewModel(repository) as T
    }
}

private fun getPokemonRepository(): PokemonRepository {
    if (offlineMode()) {
        return DatabasePokemonService()
    } else {
        return RetrofitPokemonService()
    }
}

تست پذیری برنامه

وقتی واکشی داده ها را به کمک یک interface عملی می سازیم عملیات تست پذیری یا unit testing آسان تر می شود. برای تست آن می توانیم از فریم ورک mockito استفاده کنیم.

class DetailActivityViewModelTest {
    ...

    private val mockRepository = mock(PokemonRepository::class.java)

    @Test
    fun loadData() {
        val testPokemon = Pokemon(name = "Nasim", types = listOf(TypeSlot(type = Type("grass"))))

        whenever(mockRepository.getPokemonDetail(anyString())).thenReturn(testPokemon)

        ...
    }

    ...
}

اگر بخواهید طراحی الگوی repository را در عمل مطالعه نمایید می توانید به لینک این پروژه در گیت هاب مراجعه نمایید.

به این پست امتیاز دهید

روی ستاره های کلیک کنید و امتیاز بدید

میانگین امتیاز / 5. تعداد:

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *




Enter Captcha Here : *

Reload Image