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

重复使用Kotlin中不可变数据类的映射代码,kotlin,immutability,Kotlin,Immutability,更新:添加了评论中的一些澄清 我想对不可变的数据类的主构造函数和copy()方法使用相同的“映射”代码。如果不先创建一个空对象,然后在其上使用copy(),我如何才能做到这一点 现在的问题是,如果我向Employee和EmployeeForm添加一个具有默认值的新属性,那么很容易只将其添加到两个映射函数中的一个,而忽略另一个(ToEmployeeNotreurable/copyEmployee) 以下是我想要映射的数据类: @Entity data class Employee( val

更新:添加了评论中的一些澄清

我想对不可变的
数据类的主构造函数和
copy()
方法使用相同的“映射”代码。如果不先创建一个空对象,然后在其上使用
copy()
,我如何才能做到这一点

现在的问题是,如果我向
Employee
EmployeeForm
添加一个具有默认值的新属性,那么很容易只将其添加到两个映射函数中的一个,而忽略另一个(
ToEmployeeNotreurable
/
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
        )
    }
}
@实体
数据类雇员(
val firstName:String,
val lastName:String,
val作业类型:Int,
@OneToMany(mappedBy=“employee”,cascade=[CascadeType.ALL],fetch=FetchType.EAGER)
private val_缺席:MutableSet=mutableSetOf(),
@Id@GeneratedValue(策略=GenerationType.IDENTITY)
var id:Long=0//阻止@Joffrey的答案工作
) {
初始化{
_absences.forEach{it.employee=this}
}
val absences get()=\u absences.toSet()
娱乐添加缺席(新缺席:缺席){
newidence.employee=此
_缺勤+=新缺勤
}
@实体
@表(name=“缺勤”)
数据类缺失(
//…省略字段
) {
@manytone(fetch=FetchType.EAGER)
@JoinColumn(name=“employee\u id”)
lateinit var雇员:雇员
}
}
数据类EmployeeForm(
var firstName:String=“”,
var lastName:String=“”,
变量作业类型:Int=0
) {
//不可重复使用
员工乐趣不可编辑():员工{
返回员工(名字、姓氏、职务类型)
}
//工作,但黑客
员工乐趣():员工{
返回copyEmployee(员工(“,”,0))
}
员工(员工:员工):员工{
返回employee.copy(
firstName=firstName,
lastName=lastName,
jobType=作业类型
)
}
}

虽然可变性很好,但在我的例子中,我想知道这是如何实现的。

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

这将遵循Kotlin设计师为
List
vs
MutableList
所做的工作

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
    )
}
但是,这意味着表单包含员工的所有字段,这可能是您不想要的


另外,我个人更喜欢您在开始时所做的,两次列出字段不会有问题,只需为您的函数编写测试,当您想要添加功能时,无论如何,您都要为该功能添加测试。

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

这将遵循Kotlin设计师为
List
vs
MutableList
所做的工作

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
    )
}
但是,这意味着表单包含员工的所有字段,这可能是您不想要的


另外,我个人更喜欢您在开始时所做的,两次列出字段不会有问题,只需为您的函数编写测试,当您想要添加功能时,无论如何,您都将为该功能添加测试。

您应该能够使用反射来执行此操作:检查
员工
员工表单
中的属性列表,通过匹配的名称调用构造函数(用于处理默认参数)。当然,缺点是,如果缺少任何属性,就不会出现编译时错误(但在这种情况下,任何测试都可能失败并告诉您该问题)

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

inlinefun复制(x:Any):T{
val construct=T::class.primaryConstructor
val props=x::class.memberProperties.associate{
//假设x上的所有属性都是构造函数的有效参数
配对(construct.findParameterByName(it.name)!!,
它。呼叫(x))
}
返回construct.callBy(props)
}
//以雇员的形式
fun toEmployee()=复制(此)

您可以使用Scala宏在编译时进行检查,但我认为在Kotlin中不可能做到这一点。

您应该能够使用反射:检查
Employee
EmployeeForm
中的属性列表,通过匹配的名称调用构造函数(用于处理默认参数)。当然,缺点是,如果缺少任何属性,就不会出现编译时错误(但在这种情况下,任何测试都可能失败并告诉您该问题)

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

inlinefun复制(x:Any):T{
val construct=T::class.primaryConstructor
val props=x::class.memberProperties.associate{
//假设x上的所有属性都是构造函数的有效参数
配对(construct.findParameterByName(it.name)!!,
它。呼叫(x))
}
返回construct.callBy(props)
}
//以雇员的形式
fun toEmployee()=复制(此)

您可以使用Scala宏进行编译时检查,但我认为在Kotlin中是不可能的。

您所说的可重用是什么意思?您希望如何重复使用它?你能举个例子吗?我现在不明白你的