转换器(Converter)设计模式

3,909 阅读4分钟
原文链接: my.oschina.net

在日常开发的时候,需要在对象之间进行值的 copy,如 POJO,DTO,VO,对象之间有相同的属性,想把一个对象的值 copy 到另一个对象中去,如 从数据库中查询出我们的 POJO 对象的数据,又有个对象是对 POJO 进行包装DTO,现在想把查询出来的 POJO 的值 copy 到 DTO 中相应的属性中去,之后再扩展其属性,对此,一般可以有三种方式进行解决:setter,转换器模式和反射,接下来就看下它们的一个区别:

在区分这三种方式之前,先要定义一下需要进行数据copy的两个类:

Person类:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Person {

    private String name;

    private int age;

    private String gender;

    private String job;
}

PersonDto类:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class PersonDto {

    private String name;

    private int age;

    private String gender;

    private String address;
}

下面的演示都是用这两个类进行。

setter

通过 setter 方式 copy 数据比较方便,但是如果在很多地方需要进行数据的 copy,就显得有点重复了,当然可以写个工具,专门对该数据进行扩展,如下所示:

       public Person copy(PersonDto personDto) {
            Person person = new Person();
            person.setName(personDto.getName());
            person.setAge(personDto.getAge());
            person.setGender(personDto.getGender());
            return person;
        }

使用该方式比较简单。

转换器(Converter)模式

接下来就到该文章的主题了,可以使用转换器模式来解决该问题,先看下该模式的一个类图:

首先 Converter 类是一个顶层的接口,定义了公共的转换方法,不同类实现该接口来定义自己的转换规则

PersonConverter 类是继承于 Converter 的,定义了从 Person 到 PersonDto 和从 PersonDto 到 Person 的一个转换规则,并向外提供接口以供使用。接下来看下 顶层接口 Converter 类的定义:

/**
 * 定义转换器
 * @ Date:Created in 下午 4:44 2018/9/27 0027
 */
public class Converter<T, U> {
    
    // 从 T 转换为 U
    private Function<T, U> fromDto;

    // 从 U 转换为 T
    private Function<U, T> fromEntity;

    public Converter(final Function<T, U> fromDto, final Function<U, T> fromEntity) {
        this.fromDto = fromDto;
        this.fromEntity = fromEntity;
    }

    public final U converterFromDto(final T dto){
        return fromDto.apply(dto);
    }

    public final T converterFromEntity(final U entity){
        return fromEntity.apply(entity);
    }

    public final List<U> batchConverterFromDto(final List<T> dtos){
        return dtos.stream().map(this::converterFromDto).collect(Collectors.toList());
    }

    public final List<T> batchConverterFromEntity(final List<U> entities){
        return entities.stream().map(this::converterFromEntity).collect(Collectors.toList());
    }
}

然后在看看 Person 类的自定义转换规则,PersonConverter类:

/**
 * Person 转换器
 * @ Date:Created in 下午 5:00 2018/9/27 0027
 */
public class PersonConverter extends Converter<PersonDto, Person> {

    public PersonConverter() {
        super(new PersonDtoFunction(), new PersonFunction());
    }
    
    // 自定义转换规则
    static class PersonDtoFunction implements Function<PersonDto, Person> {
        @Override
        public Person apply(PersonDto personDto) {
            // 可定制需要复制的属性
            Person person = new Person();
            person.setName(personDto.getName());
            person.setAge(personDto.getAge());
            person.setGender(personDto.getGender());
            return person;
        }
    }

    // 自定义转换规则
    static class PersonFunction implements Function<Person, PersonDto> {
        @Override
        public PersonDto apply(Person person) {
            // 可定制需要复制的属性
            PersonDto dto = new PersonDto();
            dto.setName(person.getName());
            dto.setAge(person.getAge());
            dto.setGender(person.getGender());
            return dto;
        }
    }
}

接下来进行测试一番:

PersonDto 转换为 Person:

        Converter<PersonDto, Person> converter = new PersonConverter();

        PersonDto personDto = new PersonDto("zhangsan", 23, "male", "chengdou");
        Person person =  converter.converterFromDto(personDto);
        
        System.out.println(person); 
        // Person(name=zhangsan, age=23, gender=male, job=null)

批量 PersonDto 转换为 Person:

        PersonDto pd1 = new PersonDto("AAA", 20, "male", "beijing");
        PersonDto pd2 = new PersonDto("BBB", 21, "female", "shanghai");
        PersonDto pd3 = new PersonDto("CCC", 22, "male", "chengdou");

        List<PersonDto> dtos = Lists.newArrayList(pd1, pd2, pd3);

        List<Person> persons = converter.batchConverterFromDto(dtos);

        persons.forEach((x) -> System.out.println(x));

结果:
Person(name=AAA, age=20, gender=male, job=null)
Person(name=BBB, age=21, gender=female, job=null)
Person(name=CCC, age=22, gender=male, job=null)

从 Perosn 转换为  PersonDto:

        Person person1 = new Person("lisi", 25, "female", "java");
        PersonDto personDto1 = converter.converterFromEntity(person1);

        System.out.println(personDto1);
        // PersonDto(name=lisi, age=25, gender=female, address=null)

批量 从 Perosn 转换为  PersonDto:

        Person p1 = new Person("DDD", 25, "male", "java");
        Person p2 = new Person("EEE", 26, "male", "python");
        Person p3 = new Person("FFF", 27, "female", "C++");

        List<Person> persons1 = Lists.newArrayList(p1, p2, p3);

        List<PersonDto> dtos1 = converter.batchConverterFromEntity(persons1);

        dtos1.forEach((x) -> System.out.println(x));

结果:

PersonDto(name=DDD, age=25, gender=male, address=null)
PersonDto(name=EEE, age=26, gender=male, address=null)
PersonDto(name=FFF, age=27, gender=female, address=null)

以上就是转换器模式的内容了,还是很好扩展的,不过,每个类需要自己定义一个转换规则,这个和写一个该类的转换工具方法有什么区别哦??

反射

第三种 copy 数据的方法就是反射了,使用反射后,可以复制所有的类的数据,不用每个类专门写工具方法和转换器了,如下所示:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 *
 * @ Date:Created in 上午 9:27 2018/9/28 0028
 */
public final class BeanDataConverter {

    public static void converterData(Object fromBean, Object toBean, String[] excludeProperties)
            throws InvocationTargetException, IllegalAccessException {

        Objects.requireNonNull(fromBean);
        Objects.requireNonNull(toBean);
        Objects.requireNonNull(excludeProperties);

        List<String> excludes = Arrays.stream(excludeProperties).map(String::toLowerCase).collect(Collectors.toList());

        Method[] methods = fromBean.getClass().getMethods();
        for (Method method : methods) {

            String methodName = method.getName();
            if (!methodName.startsWith("get") || "getClass".equals(methodName)
                    || excludes.contains(methodName.replaceFirst("get", "").toLowerCase())){
                continue;
            }
            Class<?> returnType = method.getReturnType();
            Object value = method.invoke(fromBean, new Object[]{ });
            String setMethodName = String.format("set%s", methodName.replaceFirst("get", ""));
            try {
                Method setMethod = toBean.getClass().getMethod(setMethodName, returnType);
                setMethod.invoke(toBean, value);
            } catch (NoSuchMethodException e) { }
        }
    }
}

测试一波:

        PersonDto personDto = new PersonDto("zhangsan", 23, "male", "chengdou");

        Person p4 = new Person();
        // 不排除属性,复制全部属性
        String[] excludes = {};
        BeanDataConverter.converterData(personDto, p4, excludes);

        System.out.println(p4);
        // Person(name=zhangsan, age=23, gender=male, job=null)


        Person p5 = new Person();
        // 排除 name 属性,即不复制 name 属性的值:
        String[] excludes2 = {"name"};
        BeanDataConverter.converterData(personDto, p5, excludes2);

        System.out.println(p5);
        //  Person(name=null, age=23, gender=male, job=null)

 

以上就是使用 反射来进行copy数据了,可以看到反射是很强大的,适用于所有的数据copy,个人感觉比 转换模式要好些呢。