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

时间:2019-02-27 14:10:57

标签: kotlin immutability

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

我想对不变的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
        )
    }
}

虽然可变性很好,但就我而言,我很想知道这是怎么可能的。

2 个答案:

答案 0 :(得分:1)

一种避免四次列出属性的方法是改为声明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 :(得分: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中不可能。