我如何使用 sealed 类 来描述具有关联值的有限案例集,以及较小的此类值集?

How can I use sealed classes to describe a finite set of cases with associated values, and a smaller set of such values?

我正在考虑使用密封 class 来表示一组有限的可能值。

这是一个代码生成项目的一部分,该项目将编写大量这样的 classes,每个都有很多案例。因此,我担心应用程序的大小。由于几种情况很可能具有相同的属性,因此我正在考虑使用包装器,例如:

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)
    }
}

这是我解决这个问题的方式吗?

如果是这样,我怎样才能最好地从密封 class 访问关联值的属性?这样

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

变成

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

Is this how I should approach this problem?

恕我直言,是的,但有一些语义变化,列在后面。

How can I best make the properties of the associated value accessible from the sealed class?

您可以为每个子class生成扩展或实例函数。

例如

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

这样就可以成功调用:

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

How can I reduce the app size while preserving the semantic of another case?

对于所有需要与参数类型相同的情况并使用 Kotlin contracts 来处理它们,您可以只生成一个 class。

以你的例子为例,你可以生成:

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

如你所见,class和Case2Case3现在只有一个class和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 对于只有一个后缀的情况,以避免不必要的属性。