В приложении, которым я сейчас занимаюсь, существует несколько классов для представления одной сущности. Например, у сущности “Сотрудник” есть целых три класса: EmployeeDto - для приема с сервера, Employee - для работы в приложении, EmployeeEntity - для хранения в локальной базе. У нас есть naming conventions, по которым суффикс *Dto говорит, что эта сущность будет использоваться для передачи данных по сети (скорее всего, такая сущность будет реализовывать интерфейс Serializable), суффикс *Entity говорит, что эта сущность будет использоваться для хранения в БД (у такой сущности будет много аннотаций для ORM), а сущности без суффикса используются непосредственно в бизнес логике, будем их называть доменными. Я расскажу о выбранном мной способе конвертирования данных между такими сущностями.

Введем интерфейсы:

interface DtoOf<T> {
    T dto();
}
interface EntityOf<T> {
    T entity();
}
interface DomainOf<T> {
    T domain();
}

Они очень простые, например, DtoOf - интерфейс объектов, которые умеют представлять что-то в DTO. И если у нас есть доменная сущность Entity, то, чтобы сделать из нее DTO‘шку достаточно объекта такого класса:

class EmployeeDtoOfDomain<EmployeeDto> implements DtoOf<EmployeeDto> {
    
    private final Employee employee;
    
    public EmployeeDtoOfDomain(final Employee employee) {
        this.employee = employee;
    }
    
    @Overrride
    public EmployeeDto dto() { ... }
}

...

final EmployeeDto dto = new EmployeeDtoOfDomain(entity).dto();

Название класса EmployeeDtoOfDomain говорит о том, что объект этого класса - это маппинг доменной сущности сотрудника в его DTO‘шку.

Такой подход не накладывает ограничения на то, из чего мы получаем нужный нам объект после маппинга. То есть EmployeeEntity мы можем получить хоть из EmployeeDto, или даже сразу из БД:

final EmployeeEntity entity = new EmployeeEntityOfDbConnection(
        connection, employeeId).entity();

Получившиеся мапперы с помощью такого подхода занимаются только одной задачей: например, маппингом доменной сущности в DTO. И если нужно сделать обратный маппинг, то этим должен и будет заниматься другой объект.