You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为DTO转Model转换器实现多态行为?优化现有转换代码

问题描述

我有一个接收BaseDto的接口,代码如下:

@JsonTypeInfo(...)
@JsonSubTypes({
    @JsonSubTypes.Type(...),
    ...
})
@RequestMapping(
    method = RequestMethod.POST,
    value = "/path",
    produces = { "application/json" },
    consumes = { "application/json" }
)

fun foo(@Valid @RequestBody dto: BaseDto ): ResponseEntity<*> {
   ...
}

DTO存在继承体系:

class Child1Dto : BaseDto
class Child2Dto : BaseDto
class Child3Dto : BaseDto
...

因此在控制器中实际会得到Child1Dto、Child2Dto或Child3Dto实例。

同时我有BaseModel及其子类:

class Child1Model : BaseModel
class Child2Model : BaseModel
class Child3Model : BaseModel
...

目前我使用如下代码实现转换:

fun BaseDto.toModel(): BaseModel =
    when (this) {
        is Child1Dto -> Child1Model(/*no args*/)
        is Child2Dto  -> Child2Model(/*no args*/)
        is Child3Dto -> Child3Model(/*some args from dto*/)
        else -> throw IllegalStateException("Unknown dto type ${this::class}")
    }

我不喜欢这段代码的原因是:

  1. DTO依赖了Model
  2. when分支代码不够优雅,我希望用多态替代但不知如何实现

请问有没有更优的代码改写方式?


优化方案

方案一:依赖反转+多态实现

核心是让DTO只依赖抽象转换契约,把转换逻辑分散到各DTO子类中,同时避免直接依赖Model。

  1. 定义抽象转换接口(可以放在DTO和Model都能访问的公共模块):
interface ModelConverter<T : BaseModel> {
    fun convert(): T
}
  1. 让BaseDto实现该接口:
abstract class BaseDto : ModelConverter<BaseModel>
  1. 每个DTO子类实现自己的转换逻辑:
class Child1Dto : BaseDto() {
    override fun convert(): Child1Model {
        return Child1Model()
    }
}

class Child2Dto : BaseDto() {
    override fun convert(): Child2Model {
        return Child2Model()
    }
}

class Child3Dto(val param: String) : BaseDto() {
    override fun convert(): Child3Model {
        return Child3Model(param)
    }
}

之后在控制器里直接调用dto.convert()就能得到对应Model,完全用多态替代了when分支。如果要严格解耦DTO和Model,可以把转换逻辑委托给外部类,比如用Spring的@Autowired注入转换服务到DTO(需要DTO被Spring管理)。

方案二:策略模式+转换工厂

把转换逻辑从DTO中彻底剥离,放到独立的策略类里,由工厂统一管理,完全解耦DTO与Model。

  1. 定义转换策略接口:
interface DtoToModelStrategy<T : BaseDto, U : BaseModel> {
    fun supports(dto: BaseDto): Boolean
    fun convert(dto: T): U
}
  1. 为每个DTO实现对应策略:
class Child1DtoStrategy : DtoToModelStrategy<Child1Dto, Child1Model> {
    override fun supports(dto: BaseDto): Boolean {
        return dto is Child1Dto
    }

    override fun convert(dto: Child1Dto): Child1Model {
        return Child1Model()
    }
}

class Child2DtoStrategy : DtoToModelStrategy<Child2Dto, Child2Model> {
    override fun supports(dto: BaseDto): Boolean {
        return dto is Child2Dto
    }

    override fun convert(dto: Child2Dto): Child2Model {
        return Child2Model()
    }
}

class Child3DtoStrategy : DtoToModelStrategy<Child3Dto, Child3Model> {
    override fun supports(dto: BaseDto): Boolean {
        return dto is Child3Dto
    }

    override fun convert(dto: Child3Dto): Child3Model {
        return Child3Model(dto.param)
    }
}
  1. 实现转换工厂:
class DtoModelConverter(private val strategies: List<DtoToModelStrategy<*, *>>) {
    fun convert(dto: BaseDto): BaseModel {
        val strategy = strategies.firstOrNull { it.supports(dto) }
            ?: throw IllegalStateException("Unknown dto type ${dto::class}")
        @Suppress("UNCHECKED_CAST")
        return (strategy as DtoToModelStrategy<BaseDto, BaseModel>).convert(dto)
    }
}
  1. Spring环境下注册Bean:
@Configuration
class ConverterConfig {
    @Bean
    fun child1DtoStrategy(): DtoToModelStrategy<*, *> = Child1DtoStrategy()

    @Bean
    fun child2DtoStrategy(): DtoToModelStrategy<*, *> = Child2DtoStrategy()

    @Bean
    fun child3DtoStrategy(): DtoToModelStrategy<*, *> = Child3DtoStrategy()

    @Bean
    fun dtoModelConverter(strategies: List<DtoToModelStrategy<*, *>>): DtoModelConverter {
        return DtoModelConverter(strategies)
    }
}

控制器中注入DtoModelConverter,调用converter.convert(dto)即可完成转换。新增DTO子类时,只需要新增对应的策略类并注册Bean,无需修改原有转换逻辑。

方案三:使用映射框架(如MapStruct)

如果允许引入第三方框架,MapStruct可以自动生成转换代码,减少手动编写的工作量,同时解耦DTO与Model。

  1. 引入依赖(Kotlin需额外配置插件):
// build.gradle.kts
plugins {
    kotlin("plugin.jpa") version "1.9.20"
    id("org.mapstruct") version "1.5.5.Final"
}

dependencies {
    implementation("org.mapstruct:mapstruct:1.5.5.Final")
    kapt("org.mapstruct:mapstruct-processor:1.5.5.Final")
}

kapt {
    arguments {
        arg("mapstruct.defaultComponentModel", "spring")
    }
}
  1. 定义映射接口:
@Mapper(componentModel = "spring")
interface DtoModelMapper {
    fun convertChild1(dto: Child1Dto): Child1Model
    fun convertChild2(dto: Child2Dto): Child2Model
    fun convertChild3(dto: Child3Dto): Child3Model

    @Named("convertDto")
    fun convert(dto: BaseDto): BaseModel {
        return when (dto) {
            is Child1Dto -> convertChild1(dto)
            is Child2Dto -> convertChild2(dto)
            is Child3Dto -> convertChild3(dto)
            else -> throw IllegalStateException("Unknown dto type ${dto::class}")
        }
    }
}

MapStruct会自动生成子类转换的实现代码,控制器中注入DtoModelMapper后,调用mapper.convert(dto)即可。新增DTO子类时,只需添加对应的转换方法,无需修改核心逻辑。


内容的提问来源于stack exchange,提问作者gstackoverflow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:47:32