玖叶教程网

前端编程开发入门

Kotlin 中的数据映射

在每个项目中,总有需要将数据从一个类映射到另一个类的时刻。特别是在使用清晰架构时,App和数据层分别有独立的模型。让我们看看 Kotlin 中映射模型的多种方法以及它们有何不同。

一个示例:有一个 UserEntity 的data层模型和一个 User 的domain(域)层模型,你需要将它们相互转换:

// 数据模型
data class UserEntity(
    val name: String,
    val surname: String,
) 
// 域模型
data class User(
    val name: String,
    val surname: String,
)

1. 扩展函数

使用扩展函数来创建一个名为 toModel()toEntity() 的转换函数是一个简单的方法。扩展函数通常存储在一个按上下文(例如按包)分类的文件中,如下所示:

// 扩展函数映射器
fun UserEntity.toModel() = User(
    name = name,
    surname = surname,
)
fun User.toEntity() = UserEntity(
    name = name,
    surname = surname,
)
// 使用
fun main() {
    val userEntity = UserEntity(name = "Name", surname = "Surname")
    val user = userEntity.toModel() // 获取模型
    user.toEntity() // 将模型反转以获得实体
}

它们相当酷且直观,但它们最大的缺点是它们不是通用的,意味着我们不能编写通用的映射操作(在本文稍后你会看到这意味着什么)。然而,它们的一个显著优点是,它们很容易被集成到现有的代码库中,因为它们是非侵入性的,添加它们的成本很低。

2. 构造函数

另一种在 Kotlin 中进行映射的方法是使用额外的构造函数:

// 构造函数映射器
data class UserEntity(
    val name: String,
    val surname: String,
) {
    constructor(model: User) : this(name = model.name, surname = model.surname)
    // 我们无法为数据库到模型的映射创建映射器,因为
    // User 在域层,不知道 UserEntity 的存在
    // 我们需要这种变通方法
    fun toModel() = User(name = name, surname = surname)
}

它们最大的缺点是它们在类内部,意味着如果需要为这个类创建多个映射器,它会大大增加类的大小。此外,你将无法从外部框架映射一个类,而从外部框架映射类并不罕见。

不建议在项目中使用这种方法,因为它们是侵入性的且受到限制。

3. 映射器接口(Mapper interface)

非常简单:创建一个映射器接口,每个映射类将实现该接口。

// 接口映射器
// 由于这个接口将只有一个抽象函数
// 我们可以使用 fun interface
fun interface Mapper<in From, out To> {
    fun map(from: From): To
}
// 根据需要,这些可以是对象或类
object UserEntityToModelMapper : Mapper<UserEntity, User> {
    override fun map(from: UserEntity) = User(
        name = from.name,
        surname = from.surname,
    )
}
object UserToEntityMapper : Mapper<User, UserEntity> {
    override fun map(from: User) = UserEntity(
        name = from.name,
        surname = from.surname,
    )
}
// 使用
fun main() {
    val userEntity = UserEntity(name = "Name", surname = "Surname")
    val user = UserEntityToModelMapper.map(userEntity) // 获取模型
    UserToEntityMapper.map(user) // 反转以获得实体
}

乍一看,这种方法相对于扩展函数并没有实际优势,而且需要编写更多的代码。然而,我们能够为集合等写通用的映射算法:

// 首先调用 List 上的 map
// 然后调用 Mapper 上的 map
fun <F, T> Mapper<F, T>.mapAll(list: List<F>) = list.map { map(it) }
// 如果需要,你可以添加更多的集合映射操作
// 使用
fun main() {
    val userEntities = listOf(
        UserEntity(name = "Name1", surname = "Surname1"),
        UserEntity(name = "Name2", surname = "Surname2"),
    )
    val users = UserEntityToModelMapper.mapAll(userEntities)
}

此外,如果与依赖注入结合使用,可以根据需模仿这些映射器。它们的初始化成本较高,但从长远来看,这种方法十分有利。

当启动新项目时,推荐使用接口方法,因为从这三种方法中,它的回报最大。在现有代码库中工作且重构时间有限时,考虑使用扩展函数。如果将来需要使用接口方法,从扩展函数转换也是非常容易的。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言