Kotlin, как объединить различные получатели через с предложением, не повторяя его - PullRequest
0 голосов
/ 27 апреля 2019

У меня есть система Parent-Search-Child, как показано ниже:

class Room

class Building {
    fun find(by: By) = Room()
}

sealed class By {

    abstract fun search(): Room

    class ById(id: String) : By() {
        override fun search(): Room = Room() // epic search method
    }

    class ByName(name: String) : By() {
        override fun search(): Room = Room() // epic search method
    }

    class Byurpose(purpose: String) : By() {
        override fun search(): Room = Room() // epic search method
    }

    companion object {
        fun id(id: String) = ById(id)
        fun name(name: String) = ByName(name)
        fun purpose(purpose: String) = Byurpose(purpose)
    }
}

Что можно использовать следующим образом:

val building = Building()
val room = building.find(By.name("Toilet"))

Однако я не очень доволен текущим синтаксисом, который может быть гораздо менее многословным в Kotlin. Кроме того, building.find может появляться в коде тысяч раз. Я мог бы реализовать это по-другому, но на самом деле у меня нет классов Room, Building или By, поэтому я не могу. Таков был мой подход:

Я реализовал класс контекста, в котором хранится ссылка Building, и использую его внутри как источник для методов поиска:

class BuildingContext(private val building: Building) {
    fun String.findById() = building.find(By.id(this))
    fun String.findByName() = building.find(By.name(this))
    fun String.findByPurpose() = building.find(By.purpose(this))
}

Может использоваться следующим образом:

with(BuildingContext(building)) {
    val room2 = "Toilet".findByName()
}

После этого я заметил, что я использую только один метод поиска в 99% случаев, поэтому (ради еще более короткого синтаксиса!) Я реализовал следующие классы:

object AlwaysSearchById {
    fun String.find(building: Building) = building.find(By.id(this))
}

object AlwaysSearchByName {
    fun String.find(building: Building) = building.find(By.name(this))
}

object AlwaysSearchByPurpose {
    fun String.find(building: Building) = building.find(By.purpose(this))
}

Что можно использовать таким образом:

with(AlwaysSearchByName) {
    val room3 = "Toilet".find(building)
}

К сожалению, ссылка на здание появляется снова. Идеальный синтаксис был бы "Toilet".find(). Я мог бы исправить это, повторно реализуя Always~ классы следующим образом:

class AlwaysSearchByNameV2(private val building: Building) {
    fun String.find() = building.find(By.name(this))
}

И это будет использоваться, как показано ниже:

with(AlwaysSearchByNameV2(building)) {
    val room = "Toilet".find()
}

Но в некоторых случаях я хотел бы также получить доступ к BuildingContext методам, поэтому я должен написать:

with(BuildingContext(building)) {
    with(AlwaysSearchByNameV2(building)) {
        val toilet = "Toilet".find()
        val randomRoom = "123".findById()
    }
}

Вопрос в том, как сократить несколько with предложений в этом случае?

В приведенном выше примере есть только 2 with предложения, но это только базовый пример. В реальном мире их может быть десятки, и написание with(with(with(with(with... наверняка будет болезненным.

На боковой ноте это не работает:

with(BuildingContext(building), AlwaysSearchByNameV2(building)) {
    val toilet = "Toilet".find()
    val randomRoom = "123".findById()
}

ни этот

with(*arrayOf(BuildingContext(building), BuildingContext(building))) {
    val toilet = "Toilet".find()
    val randomRoom = "123".findById()
}

1 Ответ

0 голосов
/ 27 апреля 2019

Вы можете написать пользовательские функции определения объема вместо того, чтобы постоянно полагаться на with. Например, вы можете добавить функцию расширения, которая будет запускать блок кода в области действия объекта AlwaysSearchByNameV2:

inline fun BuildingContext.byName(f : AlwaysSearchByNameV2.() -> Unit) = AlwaysSearchByNameV2(building).apply(f)

И используйте это:

with(BuildingContext(building)) {  // this: BuildingContext
    byName {  // this: AlwaysSearchByNameV2
        val toilet = "Toilet".find()
        val randomRoom = "123".findById()  // can still refer to BuildingContext
    }
    // back to this: BuildingContext
}
...