Как реализовать доменные сущности в Kotlin? - PullRequest
1 голос
/ 18 апреля 2020

На основании официальной документации и этого правила обнаружения я понимаю, что классы данных следует скорее использовать в качестве DTO и не должны содержать никакого поведения / logi c.

Однако для моих доменных сущностей (которые содержат как данные, так и поведение) я все же хотел бы иметь такие функции, как автоматизация c toString, equals и объявления о деструктуризации.

Я могу легко достичь этого с помощью классов данных, но, исходя из вышесказанного, это кажется семантически неверным:

data class Person(
    private var name: String,
    val age: Int
) {
    fun isAdult() = age >= 18

    fun changeName(newName: String) {
        this.name = newName
    }
}

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

1 Ответ

0 голосов
/ 18 апреля 2020

Я думаю, что есть две важные темы:

Неизменность

Я бы принял неизменность и не предоставлял какой-либо метод мутации в классах домена. Вы можете использовать метод copy для создания нового экземпляра с различными неизменяемыми свойствами.

Идентичность

Класс данных использует все свойства, объявленные в конструкторе в методе equals. Это означает, что это класс значений, то есть он не имеет идентичности. Это значение, подобное Point (x, y). Два экземпляра Point (1,2) и Point (1,2) неразличимы. Они одинаковы, если все их свойства одинаковы.

Заключение

Используйте обычный class для доменной сущности, такой как Пользователь . Используйте свойства, которые определяют личность вашей сущности, для реализации equals, hashcode, et c. Используйте data class для значения домена, такого как Address . Для класса данных вполне нормально иметь функции, если они не содержат мутаций.

В вашем случае я бы реализовал нечто подобное:

data class Address(val streetName: String)

class Person(
    val id: Int,
    val name: String,
    val age: Int,
    val address: Address
) {
    fun isAdult() = age >= 18

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Person

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id
    }

    fun copy(
        id: Int = this.id, 
        name: String = this.name, 
        age: Int = this.age,
        address: Address = this.address) = Person(id, name, age, address)

    override fun toString(): String {
        return "Person(id=$id, name='$name', age=$age, address=$address)"
    }
}


fun main() {
    val personA = Person(123312, "Person A", 17, Address("Street 1"))
    println(personA.isAdult())
    println(personA.age)

    val olderPersonA = personA.copy(age = 18)
    println(olderPersonA.isAdult())
    println(olderPersonA.age)
}

РЕДАКТИРОВАТЬ: Как вы сказали, есть возможность использовать класс данных и переопределять только equals и hashcode, но я не уверен, что это семантически правильно. Комментарии по этому поводу приветствуются.

data class Person(
    val id: Int,
    val name: String,
    val age: Int,
    val address: Address
) {
    fun isAdult() = age >= 18

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Person

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id
    }
}
...