首页 > 解决方案 > 如何在 Kotlin 中实现域实体?

问题描述

根据官方文档和这个detekt 规则,我的理解是数据类应该用作 DTO,并且不应该包含任何行为/逻辑。

但是,对于我的域实体(包含数据和行为),我仍然希望拥有诸如自动toStringequals解构声明之类的功能。

我可以使用数据类轻松实现这一点,但基于上述内容,这在语义上似乎是不正确的:

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

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

为此目的使用数据类本质上是错误的吗?是否有另一种方法可以保留这些功能但同时在语义上正确?

标签: kotlindomain-driven-designdtodata-class

解决方案


我认为有两个重要的话题:

不变性

我会接受不变性并且不在域类中提供任何突变方法。您可以使用copy方法创建具有不同不可变属性的新实例。

身份

Data 类使用 equals 方法的构造函数中声明的所有属性。这意味着它是一个值类,即它没有标识。它是一个类似于 Point(x,y) 的值。两个实例 Point(1,2) 和 Point(1,2) 无法区分。如果它们的所有属性都相同,则它们是相同的。

结论

class对像User这样的域实体使用正则。使用定义实体身份的属性来实现equals,hashcode等。将 adata 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)
}

编辑:正如你所说,可以选择使用数据类并且只覆盖等于和哈希码,但我不确定它在语义上是否正确。对此的评论表示赞赏。

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
    }
}

推荐阅读