Повторно использовать код отображения для неизменяемого класса данных в Котлине - PullRequest
0 голосов
/ 27 февраля 2019

Обновлено: добавлено несколько пояснений из комментариев

Я хотел бы использовать тот же код отображения для основного конструктора и метод copy() неизменяемого data class.Как я могу сделать это, не создавая сначала пустой объект, а затем используя copy() на нем?

Проблема заключается в том, что если я добавлю новый атрибут со значением по умолчанию к Employee иEmployeeForm было бы легко добавить его только в одну из двух функций отображения и забыть о другой (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 голосов
/ 27 февраля 2019

Вы должны быть в состоянии сделать это с помощью отражения: проверьте список свойств в Employee и EmployeeForm, вызовите конструктор по соответствующим именам (используя 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, но я не думаю, чтов Котлине это возможно.

0 голосов
/ 27 февраля 2019

Один из способов избежать перечисления атрибутов 4 раза - объявить Employee в качестве интерфейса и использовать «изменяемую» версию, форму, как единственный класс данных, реализующий его.У вас будет представление «только для чтения» с использованием интерфейса, но технически вы будете использовать только изменяемый экземпляр за кулисами.

Это будет соответствовать тому, что дизайнеры Kotlin сделали для List против 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
    )
}

Однако это означает, что в форме есть все поля сотрудника, которые вы, вероятно, не хотите.

Кроме того, я бы лично предпочел то, что вы сделали в началеперечисление дважды поля не будет проблемой, просто напишите тесты для своих функций, а когда вы захотите добавить функциональность, вы все равно добавите тесты для этой функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...