Kotlin 伴生对象 - Init 块 - Typealias

Kotlin Companion Object - Init Block - Typealias

我正在尝试解决一个 Kotlin 问题,它有一个混合的 OOP 结构,所以我无法解决它,抱歉,如果这个问题是基本的。我来自 Java,所以我是 Kotlin 的新手。

我有 DataSource.kt,它有 DataSourcePersonFetchResponseFetchError class。我不明白的是 FetchCompletionHandler typealias

import android.os.Handler
import android.os.Looper
import kotlin.collections.ArrayList
import kotlin.math.min
import kotlin.random.Random


data class Person(val id: Int, val fullName: String)

data class FetchResponse(val people: List<Person>, val next: String?)

data class FetchError(val errorDescription: String)

typealias FetchCompletionHandler = (FetchResponse?, FetchError?) -> Unit

private data class ProcessResult(val fetchResponse: FetchResponse?, val fetchError: FetchError?, val waitTime: Double)

class DataSource {
    companion object {
        private var people: List<Person> = listOf()
    }

    private object Constants {
        val peopleCountRange: ClosedRange<Int> = 100..200 // lower bound must be > 0, upper bound must be > lower bound
        val fetchCountRange: ClosedRange<Int> = 5..20 // lower bound must be > 0, upper bound must be > lower bound
        val lowWaitTimeRange: ClosedRange<Double> = 0.0..0.3 // lower bound must be >= 0.0, upper bound must be > lower bound
        val highWaitTimeRange: ClosedRange<Double> = 1.0..2.0 // lower bound must be >= 0.0, upper bound must be > lower bound
        const val errorProbability = 0.05 // must be > 0.0
        const val backendBugTriggerProbability = 0.05 // must be > 0.0
        const val emptyFirstResultsProbability = 0.1 // must be > 0.0
    }

    init {
        initializeData()
    }

    public fun fetch(next: String?, completionHandler: FetchCompletionHandler) {
        val processResult = processRequest(next)

        Handler(Looper.getMainLooper()).postDelayed({
            completionHandler(processResult.fetchResponse, processResult.fetchError)
        },(processResult.waitTime * 1000).toLong())
    }

    private fun initializeData() {
        if (people.isNotEmpty()) {
            return
        }
        val newPeople: ArrayList<Person> = arrayListOf()
        val peopleCount: Int = RandomUtils.generateRandomInt(range = Constants.peopleCountRange)
        for (index in 0 until peopleCount) {
            val person = Person(id = index + 1, fullName = PeopleGen.generateRandomFullName())
            newPeople.add(person)
        }
        people = newPeople.shuffled()
    }

    private fun processRequest(next: String?): ProcessResult {
        var error: FetchError? = null
        var response: FetchResponse? = null
        val isError = RandomUtils.roll(probability = Constants.errorProbability)
        val waitTime: Double
        if (isError) {
            waitTime = RandomUtils.generateRandomDouble(range = Constants.lowWaitTimeRange)
            error = FetchError(errorDescription = "Internal Server Error")
        } else {
            waitTime = RandomUtils.generateRandomDouble(range = Constants.highWaitTimeRange)
            val fetchCount = RandomUtils.generateRandomInt(range = Constants.fetchCountRange)
            val peopleCount = people.size
            val nextIntValue = try {
                next!!.toInt()
            } catch (ex: Exception) {
                null
            }
            if (next != null && (nextIntValue == null || nextIntValue < 0)) {
                error = FetchError(errorDescription = "Parameter error")
            } else {
                val endIndex: Int = min(peopleCount, fetchCount + (nextIntValue ?: 0))
                val beginIndex: Int = if (next == null) 0 else min(nextIntValue!!, endIndex)
                var responseNext: String? = if (endIndex >= peopleCount) null else endIndex.toString()
                var fetchedPeople: ArrayList<Person> = ArrayList(people.subList(beginIndex, endIndex)) // begin ile end ayni olunca bos donuyor mu?
                if (beginIndex > 0 && RandomUtils.roll(probability = Constants.backendBugTriggerProbability)) {
                    fetchedPeople.add(0, people[beginIndex - 1])
                } else if (beginIndex == 0 && RandomUtils.roll(probability = Constants.emptyFirstResultsProbability)) {
                    fetchedPeople = arrayListOf()
                    responseNext = null
                }
                response = FetchResponse(people = fetchedPeople, next = responseNext)
            }
        }
        return ProcessResult(response, error, waitTime)
    }
}

// Utils
private object RandomUtils {

    fun generateRandomInt(range: ClosedRange<Int>): Int = Random.nextInt(range.start, range.endInclusive)

    fun generateRandomDouble(range: ClosedRange<Double>): Double = Random.nextDouble(range.start, range.endInclusive)

    fun roll(probability: Double): Boolean {
        val random = Random.nextDouble(0.0, 1.0)
        return random <= probability
    }
}

private object PeopleGen {
    private val firstNames = listOf("Fatma", "Mehmet", "Ayşe", "Mustafa", "Emine", "Ahmet", "Hatice", "Ali", "Zeynep", "Hüseyin", "Elif", "Hasan", "İbrahim", "Can", "Murat", "Özlem")
    private val lastNames = listOf("Yılmaz", "Şahin", "Demir", "Çelik", "Şahin", "Öztürk", "Kılıç", "Arslan", "Taş", "Aksoy", "Barış", "Dalkıran")

    fun generateRandomFullName(): String {
        val firstNamesCount = firstNames.size
        val lastNamesCount = lastNames.size
        val firstName = firstNames[RandomUtils.generateRandomInt(range = 0 until firstNamesCount)]
        val lastName = lastNames[RandomUtils.generateRandomInt(range = 0 until lastNamesCount)]
        return "${firstName} ${lastName}"
    }
}

在文档中,我看到了这些规则;

1) In order to fetch first set of people, use `DataSource`s `fetch` method by passing null into its `next` parameter and a completion handler.
2) "Completion Handler" has 2 parameters in its signature. A response or an error instance is passed when the operation finishes. They can't be both null or hold non-null references at a time.
3) When success, `FetchResponse` instance is passed into completion handler. This instance has people array and a `next` identifier which can be used for pagination.
4) Pass successful response's `next` identifier into `fetch` method in order to get next "pages".

在我的 Main class 中,我只是想获取 people 的列表以及他们的 id,我是这样尝试的;

import DataSource
import FetchCompletionHandler
import FetchResponse
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val people = DataSource
        val fetchResponse: FetchResponse(people,null)
    }
}

或喜欢;

        var fetchCompletionHandler:FetchCompletionHandler? = null
        var myHandler = fetchCompletionHandler?.let { DataSource().fetch(null, it) }
        var mySource = DataSource
        var x = FetchResponse(mySource,null)

但它表明那是错误的,所以我一开始就卡住了。我该如何解决这个问题?

DataSource 是一个 class,而不是一个 object,因此您必须先实例化它才能使用它。它具有伴随对象这一事实与您无关,因为伴随对象没有任何 public 属性或函数。

FetchCompletionHandler是一个回调函数。 typealias 只是让代码更容易描述,而不必在每次需要提及类型时都编写 (FetchResponse?, FetchError?) -> Unit

由于回调是 fetch 函数的最后一个参数,您可以使用尾随 lambda 语法调用它。在这种情况下,lambda 是您的 FetchCompletionHandler。 lambda 的两个参数是 FetchResponse?FetchError?,定义在 typealias.

因此,您可以这样使用它:

class MainActivity : AppCompatActivity() {

    private val dataSource = DataSource()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        dataSource.fetch(null) { response, error ->
            // do something with the FetchResponse? and FetchError? here when they arrive
            when {
                response != null -> { }
                error!= null -> { }
            }
        }
    }
}

您实际上应该将 DataSource 放在 ViewModel 中,这样它就不必在每次屏幕旋转时都重新创建。由于它支持分页,所以你不想在屏幕旋转时丢失你的位置。

您可以将函数分配给 属性:

而不是尾随 lambda 语法
val callback: FetchCompletionHandler = { response, error ->
    //...
}

// ...
dataSource.fetch(null, callback)

或者您可以编写一个成员函数并使用函数引用语法传递它 ::

private fun handleFetch(response: FetchResponse?, error: FetchError?) {
    //...
}

// ...
dataSource.fetch(null, ::handleFetch)