Kotlin 将构造函数参数传递给父级而不在子级中声明

Kotlin pass through constructor parameters to parent without declaring in child

我的用例: 我有大量的 POJO 模型,它们是针对第三方的不同类型的请求 API。他们都有几个共同的领域和几个独特的领域。

我希望构建概念上看起来像这样的东西

class RequestBase(
  val commonField1: String,
  val commonField2: String,
  ...
  val commonFieldX: String
)

class RequestA(
  val uniqueFieldA: String
): RequestBase()

class RequestB(
  val uniqueFieldB: String
): RequestBase()

fun main() {
  val requestA = RequestA(
    commonField1 = "1",
    commonField2 = "2",
    ...
    uniqueFieldA = "A"
  )
}

我当然可以覆盖每个子请求中的公共字段,然后将它们传递给父构造函数,但这最终会产生大量样板代码并使模型膨胀。我可以在这里探索任何选项吗?

你不能用基础 class 的构造函数来做。没有构造函数是可能的:

open class RequestBase {
    lateinit var commonField1: String
    lateinit var commonField2: String
    ...
    lateinit var commonFieldX: String
}

class RequestA(
    val uniqueFieldA: String
): RequestBase()

class RequestB(
    val uniqueFieldB: String
): RequestBase()

fun main() {
    val requestA = RequestA(
        uniqueFieldA = "A"
    ).apply {
        commonField1 = "1"
        commonField2 = "2"
        ...
        commonFieldX = "X"
    }
}

请注意,您在 class 声明后的括号中所做的不是“声明此 class 具有的属性”,而是“声明此 class 的参数'主要构造函数'”。前者只是你可以通过添加 varval.

“一路走来”做的事情

每个 class 都可以有自己的主构造函数,可以接受任意数量和类型的参数,而不管 class 它的 superclass 是什么。因此,必须指定构造函数的所有参数并不是没有道理的:

open class RequestBase(
  val commonField1: String,
  val commonField2: String,
  ...
  val commonFieldX: String
)

class RequestA(
  // notice that the parameters for the inherited properties don't have the
  // "val" prefix, because you are not declaring them in the subclass again.
  // These are just constructor parameters.
  commonField1: String, 
  commonField2: String,
  ...
  commonFieldX: String,
  val uniqueFieldA: String,
): RequestBase(
  commonField1,
  commonField2,
  ...
  commonFieldX,
)

如果您觉得这不愉快,有很多方法可以解决这个问题。

一种方法是使用组合和委托——创建一个具有公共属性的接口。特定请求的主要构造函数将采用 RequestBase 及其独特的属性,并通过委托给 RequestBase:

来实现接口
interface Request {
    val commonField1: String
    val commonField2: String
    val commonFieldX: String
}

open class RequestBase(
    override val commonField1: String,
    override val commonField2: String,
    override val commonFieldX: String
): Request

class RequestA(
    val requestBase: RequestBase,
    val uniqueField: String
): Request by requestBase

这允许您直接访问someRequestA.commonFieldX,而无需执行someRequestA.requestBase.commonFieldX,但是要创建一个RequestA,您需要先创建一个RequestBase

RequestA(
    RequestBase(...),
    uniqueField = ...
)

另一种方法是将您的属性更改为 vars,给它们默认值,并将它们移出构造函数参数:

open class RequestBase {
    var commonField1: String = ""
    var commonField2: String = ""
    var commonFieldX: String = ""
}

class RequestA: RequestBase() {
    var uniqueField: String = ""
}

然后要创建 RequestA 的实例,您只需调用其无参数构造函数,然后执行 apply { ... } 块:

RequestA().apply {
    commonField1 = "foo"
    commonField2 = "bar"
    commonFieldX = "baz"
    uniqueField = "boo"
}

这样做的缺点当然是属性都是可变的,你必须为每个 属性 考虑一个默认值。因此,您可能必须将某些属性更改为可为空,这可能是不可取的。