Как я могу использовать запечатанные классы для описания конечного набора случаев со связанными значениями и меньшего набора таких значений? - PullRequest
2 голосов
/ 14 марта 2020

Я рассматриваю использование запечатанного класса для представления конечного набора возможных значений.

Это часть проекта генерации кода, который напишет очень большое количество таких классов, каждый из которых может иметь много случаев. Поэтому меня беспокоит размер приложения. Поскольку весьма вероятно, что несколько дел могут иметь одинаковые атрибуты, я рассматриваю использование оболочек, таких как:

data class Foo(val title: String, ...lot of other attributes)
data class Bar(val id: Int, ...lot of other attributes)

sealed class ContentType {
    class Case1(val value: Foo) : ContentType()
    class Case2(val value: Bar) : ContentType()

    // try to reduce app size by reusing the existing type,
    // while preserving the semantic of a different case
    class Case3(val value: Bar) : ContentType()
}

fun main() {
    val content: ContentType = ContentType.Case1(Foo("hello"))
    when(content) {
        is ContentType.Case1 -> println(content.value.title)
        is ContentType.Case2 -> println(content.value.id)
        is ContentType.Case3 -> println(content.value.id)
    }
}

Это то, как я должен подходить к этой проблеме?

Если так, Как лучше всего сделать свойства связанного значения доступными из запечатанного класса? Так что

        is ContentType.Case2 -> println(content.value.id)

становится

        is ContentType.Case2 -> println(content.id)

1 Ответ

2 голосов
/ 22 марта 2020

Это то, как я должен подходить к этой проблеме?

ИМХО, да, но с некоторыми изменениями c, перечисленными после.

Как лучше ли сделать свойства связанного значения доступными из запечатанного класса?

Вы можете создавать расширения или функции экземпляра для каждого подкласса.

например

val ContentType.Case2.id: String get() = value.id

Таким образом, вы можете успешно позвонить:

is ContentType.Case2 -> println(content.id)

Как уменьшить размер приложения, сохранив при этом семантику c другого дела?

Вы можете сделать это, сгенерировав только один класс для всех случаев, для которых нужны те же типы, что и для параметров, и использовать Kotlin contracts для их обработки.

Используя ваш пример, вы можете сгенерировать :

sealed class ContentType {
    class Case1(val value: Foo) : ContentType()
    class Case2_3(val value: Bar, val caseSuffix: Int) : ContentType()
}

Как видите, классы Case2 и Case3 теперь являются только одним классом, а caseSuffix определяет, какой из них это.

Теперь вы можете создайте следующие расширения (по одному для каждого случая):

@OptIn(ExperimentalContracts::class)
fun ContentType.isCase1(): Boolean {
    contract {
        returns(true) implies (this@isCase1 is ContentType.Case1)
    }
    return this is ContentType.Case1
}

@OptIn(ExperimentalContracts::class)
fun ContentType.isCase2(): Boolean {
    contract {
        returns(true) implies (this@isCase2 is ContentType.Case2_3)
    }
    return this is ContentType.Case2_3 && caseSuffix == 2
}

@OptIn(ExperimentalContracts::class)
fun ContentType.isCase3(): Boolean {
    contract {
        returns(true) implies (this@isCase3 is ContentType.Case2_3)
    }
    return this is ContentType.Case2_3 && caseSuffix == 3
}

Поскольку вы используете contracts клиент теперь может использовать их с:

when {
    content.isCase1() -> println(content.title)
    content.isCase2() -> println(content.id)
    content.isCase3() -> println(content.id)
}

Как видите, дальнейшая оптимизация может заключаться в удалении свойства caseSuffix для случаев с одним суффиксом, чтобы избежать ненужных свойств.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...