Kotlin中的数据映射

程序员咋不秃头 2024-08-14 03:29:42

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

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

// 数据模型data UserEntity( val name: String, val surname: String,) // 域模型data 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 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 interfacefun 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 上的 mapfun <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)}

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

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

0 阅读:18