在Kotlin中重用不可变数据类的映射代码

问题描述 投票:0回答:2

更新:从评论中添加了一些说明

我想对主要构造函数使用相同的“映射”代码,并使用不可变copy()data class方法。如何在不首先创建空对象,然后在其上使用copy()的情况下执行此操作?

现在的问题是,如果我将一个带有默认值的新属性添加到EmployeeEmployeeForm,那么很容易只将它添加到两个映射函数中的一个而忘记另一个(toEmployeeNotReusable / copyEmployee)。

这些是我想要映射的数据类:

@Entity
data class Employee(
    val firstName: String,
    val lastName: String,
    val jobType: Int,

    @OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
    private val _absences: MutableSet<Absence> = mutableSetOf(),

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0 // prevents @Joffrey's answer from working
) {
    init {
        _absences.forEach { it.employee = this }
    }

    val absences get() = _absences.toSet()

    fun addAbsence(newAbsence: Absence) {
        newAbsence.employee = this
        _absences += newAbsence
    }

    @Entity
    @Table(name = "absence")
    data class Absence(
        // ... omitted fields
    ) {
        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "employee_id")
        lateinit var employee: Employee
    }
}


data class EmployeeForm(
    var firstName: String = "",
    var lastName: String = "",
    var jobType: Int = 0
) {
    // not reusable
    fun toEmployeeNotReusable(): Employee {
        return Employee(firstName, lastName, jobType)
    }

    // works but hacky
    fun toEmployee(): Employee {
        return copyEmployee(Employee("", "", 0))
    }

    fun copyEmployee(employee: Employee): Employee {
        return employee.copy(
            firstName = firstName,
            lastName = lastName,
            jobType = jobType
        )
    }
}

虽然可变性很好,但就我而言,我有兴趣知道这是可能的。

kotlin immutability
2个回答
1
投票

避免将属性列出4次的一种方法是将Employee声明为接口,并使用“可变”版本(表单)作为实现它的唯一数据类。您可以使用该界面获得“只读”视图,但从技术上讲,您只能在幕后使用可变实例。

这将遵循Kotlin设计师为ListMutableList所做的事情。

interface Employee {
    val firstName: String
    val lastName: String
    val jobType: Int
}

data class EmployeeForm(
    override var firstName: String = "",
    override var lastName: String = "",
    override var jobType: Int = 0
): Employee {

    fun toEmployee(): Employee = this.copy()

    fun copyEmployee(employee: Employee): Employee = this.copy(
            firstName = firstName,
            lastName = lastName,
            jobType = jobType
    )
}

但是,这意味着表单包含员工的所有字段,您可能不需要。

另外,我个人更喜欢你在开始时所做的事情,列出两次字段不会有问题,只需为你的函数编写测试,当你想要添加功能时,你也会为这个功能添加测试。


1
投票

您应该能够使用反射执行此操作:检查EmployeeEmployeeForm中的属性列表,使用匹配的名称调用构造函数(使用callBy处理默认参数)。当然,缺点是如果缺少任何属性,您将不会遇到编译时错误(但是对于这种情况,任何测试都可能会失败并告诉您有关该问题的信息)。

近似和未经测试(不要忘记添加kotlin-reflect依赖项):

inline fun <reified T> copy(x: Any): T {
    val construct = T::class.primaryConstructor
    val props = x::class.memberProperties.associate { 
        // assumes all properties on x are valid params for the constructor
        Pair(construct.findParameterByName(it.name)!!,
             it.call(x))
    }
    return construct.callBy(props)
}

// in EmployeeForm
fun toEmployee() = copy<Employee>(this)

您可以创建一个使用Scala宏进行编译时检查的等效项,但我不认为它在Kotlin中是可能的。

© www.soinside.com 2019 - 2024. All rights reserved.