对于同一个Bird实体,我有两个MapStruct Mapping类,它们的方法(customCodeBirdMapping)几乎做同样的事情,但返回类型略有不同。
BirdViewMapper
@Mapper(componentModel = "spring")
public interface BirdViewMapper {
@Mapping(source = "bird.listHealthCheck", target = "listHealthCheck")
BirdViewDTO birdToBirdViewDTO(Bird bird);
@AfterMapping
default void customCodeBirdMapping(Bird source, @MappingTarget BirdViewDTO target) {
if (!source.getListTransmitter().isEmpty()) {
Transmitter trans = (source.getListTransmitter()
.stream()
.max(Comparator.comparing(Transmitter::getDateAttached)).get());
target.setChannel(trans.getChannel());
target.setChannelOffset(trans.getChannelOffset());
}
if (!source.getListPIT().isEmpty()) {
PIT pit = (source.getListPIT()
.stream()
.max(Comparator.comparing(PIT::getDateInserted)).get());
target.setPitCode(pit.getCode());
}
if (!source.getListHealthCheck().isEmpty()) {
HealthCheck healthCheck = (source.getListHealthCheck()
.stream()
.max(Comparator.comparing(HealthCheck::getCatchDate)).get());
target.setDateLastHealthCheck(healthCheck.getCatchDate());
}
}
}
BirdMapper
@Mapper(componentModel = "spring")
public interface BirdMapper {
BirdDashboardDTO birdToBirdListDTO(Bird bird);
@AfterMapping
default void customCodeBirdMapping(Bird source, @MappingTarget BirdDashboardDTO target) {
if (!source.getListTransmitter().isEmpty()) {
Transmitter trans = (source.getListTransmitter()
.stream()
.max(Comparator.comparing(Transmitter::getDateAttached)).get());
target.setChannel(trans.getChannel());
target.setChannelOffset(trans.getChannelOffset());
}
if (!source.getListPIT().isEmpty()) {
PIT pit = (source.getListPIT()
.stream()
.max(Comparator.comparing(PIT::getDateInserted)).get());
target.setPitCode(pit.getCode());
}
if (!source.getListHealthCheck().isEmpty()) {
HealthCheck healthCheck = (source.getListHealthCheck()
.stream()
.max(Comparator.comparing(HealthCheck::getCatchDate)).get());
target.setDateLastHealthCheck(healthCheck.getCatchDate());
}
}
}
请注意,BirdViewMapper和BirdMapper都使用@AfterMapping
来设置一些子实体的最近值,但返回/目标类型不同。
Bird dto用于不同的视图,一个用于鸟类列表(只有几个字段),一个用于单个鸟类的视图(多个字段)。
我在我的映射器类上使用customCodeBirdMapping
方法违反了DRY规则-有没有更好的方法-一种避免重复的方法?
在您的两个示例中,您展示了您有一个用于某种类型的birdto的Mapper。有多种方法可以解决你的问题。
-
将两个映射器合并为一个。反正他们是有关系的。然后只需在映射器中编写常用的java私有方法。这些可以在@AfterMapping方法中调用。这样可以防止代码重复。最简单的方法显然是让dto继承值。
@Data public class BirdDTOParent{ private Channel channel; // fields shared by BirdViewDTO and BirdDashboardDTO } @Data public class BirdViewDTO exntends BirdDTOParent{ // specific fields to BirdViewDTO } @Data public class BirdDashboardDTO extends BirdDTOParent{ // specific fields to BirdDashboardDTO }
和mapper中的方法:
@AfterMapping default void customCodeBirdMapping(Bird source, @MappingTarget BirdDashboardDTO target) { mappingHelper(source,target); } private void mappingHelper(Bird source, BirdDTOParent target) { if (!source.getListTransmitter().isEmpty()) { Transmitter trans = (source.getListTransmitter() .stream() .max(Comparator.comparing(Transmitter::getDateAttached)).get()); target.setChannel(trans.getChannel()); target.setChannelOffset(trans.getChannelOffset()); } ... }
如果你不想这样做,那么你可以在运行时强制转换。然而,这要丑陋得多。如果dto有这么多共同点,你应该让它们继承
的值。private void mappingHelper(Bird source, Object target) { if (!source.getListTransmitter().isEmpty()) { Transmitter trans = (source.getListTransmitter() .stream() .max(Comparator.comparing(Transmitter::getDateAttached)).get()); if(target instanceof BirdDashboardDTO ) { ((BirdDashboardDTO) target).setChannel(trans.getChannel()); ((BirdDashboardDTO) target).setChannelOffset(trans.getChannelOffset()); } else if (target instanceof BirdViewDTO) { ((BirdViewDTO) target).setChannel(trans.getChannel()); ((BirdViewDTO) target).setChannelOffset(trans.getChannelOffset()); } ... }
-
您可以让映射器使用其他映射器的逻辑。
@Mapper(componentModel = "spring", uses = {SomeOtherMapper.class}) public interface BirdMapper {
即使是你用@Named注解定义的函数,也可以在原始的和使用"mapper
-
使用静态助手类或spring bean。Mapstruct允许将spring bean注入到映射器中。为了实现这个Mapstruct,你必须将Mapper从一个接口更改为一个抽象类——我如何在生成的Mapper类中注入spring依赖项呢?
我认为最干净的方法是第一个。但我想这取决于你:)
编辑
第一种方法可以进一步细化,为父类创建一个@AfterMapping方法,并为每个子实现添加@AfterMapping方法
@Mapper(config = MappingConfig.class)
public interface BirdMapper {
BirdViewDTO entityToViewDTO(BirdEntity birdEntity);
BirdDashboardDTO entityToDashboardDTO(BirdEntity birdEntity);
@AfterMapping
default void mapParent(BirdEntity source, @MappingTarget BirdDTOParent target){
System.out.println("aaaaa");
}
@AfterMapping
default void mapViewDTO(BirdEntity source, @MappingTarget BirdViewDTO target){
System.out.println("bbbbbb");
}
@AfterMapping
default void mapDashboardDTO(BirdEntity source, @MappingTarget BirdDashboardDTO target){
System.out.println("cccccc");
}
}
生成的映射文件如下所示
@Generated(
value = "... mapstruct generated string ...",
date = "... mapstruct generated string ...",
comments = "... mapstruct generated string ...")
@Component
public class BirdMapperImpl implements BirdMapper {
@Override
public BirdViewDTO entityToViewDTO(BirdEntity birdEntity) {
if ( birdEntity == null ) {
return null;
}
BirdViewDTO birdViewDTO = new BirdViewDTO();
birdViewDTO.setName( birdEntity.getName() );
birdViewDTO.setViewProperty( birdEntity.getViewProperty() );
mapParent( birdEntity, birdViewDTO );
mapViewDTO( birdEntity, birdViewDTO );
return birdViewDTO;
}
@Override
public BirdDashboardDTO entityToDashboardDTO(BirdEntity birdEntity) {
if ( birdEntity == null ) {
return null;
}
BirdDashboardDTO birdDashboardDTO = new BirdDashboardDTO();
birdDashboardDTO.setName( birdEntity.getName() );
birdDashboardDTO.setDashboardProperty( birdEntity.getDashboardProperty() );
mapParent( birdEntity, birdDashboardDTO );
mapDashboardDTO( birdEntity, birdDashboardDTO );
return birdDashboardDTO;
}
}
透视
从角度来看,Dan Abramov关于WET代码库的演讲可能值得一看。
DRY或不DRY
考虑重构重复数据删除时(DRY)在一些代码中,人们应该问自己相关的代码段是否恰好相同,或者它们是否必须相同。在您的示例中,两个bird类是否根据定义共享相同的代码?如果它们不这样做,一个小的改变可能会导致两段代码分离,如果它们被合并/重复数据删除/干燥,那么DRY代码最终会成为一个整体,或者无论如何都必须分离出来。在您的示例中,根据定义,这两个类似乎是相同的,因此它们是重复数据删除的主要候选对象。然而,在大多数情况下,这两段相似的代码并不完全相同,因此答案并不那么明确。
如何干燥
在重复数据删除代码时,要小心,因为类(或一般的OOP)是一种工具,可以很容易地使所有东西看起来像钉子(这是对古老谚语的曲解)。在这种情况下,引入一个通用的超类可能很有吸引力,但静态实用函数可能更灵活、更合适。