mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-13 18:15:41 +00:00
refactor: converter pattern docs and refactoring
This commit is contained in:
1 parent
20e804ba95
commit
8f777cdc98
6 files changed
+87
-93
No files matched your search
+73
-47
@@ -1,25 +1,27 @@
|
|||||||
---
|
---
|
||||||
title: Converter
|
title: Converter
|
||||||
category: Creational
|
category: Structural
|
||||||
language: en
|
language: en
|
||||||
tag:
|
tag:
|
||||||
- Decoupling
|
- Compatibility
|
||||||
|
- Data transformation
|
||||||
|
- Object mapping
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Also known as
|
||||||
|
|
||||||
|
* Mapper
|
||||||
|
* Translator
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
The purpose of the Converter pattern is to provide a generic, common way of bidirectional
|
The purpose of the Converter pattern is to provide a generic, common way of bidirectional conversion between corresponding types, allowing a clean implementation in which the types do not need to be aware of each other. Moreover, the Converter pattern introduces bidirectional collection mapping, reducing a boilerplate code to minimum.
|
||||||
conversion between corresponding types, allowing a clean implementation in which the types do not
|
|
||||||
need to be aware of each other. Moreover, the Converter pattern introduces bidirectional collection
|
|
||||||
mapping, reducing a boilerplate code to minimum.
|
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> In real world applications it is often the case that database layer consists of entities that need
|
> In real world applications it is often the case that database layer consists of entities that need to be mapped into DTOs for use on the business logic layer. Similar mapping is done for potentially huge amount of classes, and we need a generic way to achieve this.
|
||||||
> to be mapped into DTOs for use on the business logic layer. Similar mapping is done for
|
|
||||||
> potentially huge amount of classes and we need a generic way to achieve this.
|
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -27,35 +29,34 @@ In plain words
|
|||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
We need a generic solution for the mapping problem. To achieve this, let's introduce a generic
|
We need a generic solution for the mapping problem. To achieve this, let's introduce a generic converter.
|
||||||
converter.
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class Converter<T, U> {
|
public class Converter<T, U> {
|
||||||
|
|
||||||
private final Function<T, U> fromDto;
|
private final Function<T, U> fromDto;
|
||||||
private final Function<U, T> fromEntity;
|
private final Function<U, T> fromEntity;
|
||||||
|
|
||||||
public Converter(final Function<T, U> fromDto, final Function<U, T> fromEntity) {
|
public Converter(final Function<T, U> fromDto, final Function<U, T> fromEntity) {
|
||||||
this.fromDto = fromDto;
|
this.fromDto = fromDto;
|
||||||
this.fromEntity = fromEntity;
|
this.fromEntity = fromEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final U convertFromDto(final T dto) {
|
public final U convertFromDto(final T dto) {
|
||||||
return fromDto.apply(dto);
|
return fromDto.apply(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final T convertFromEntity(final U entity) {
|
public final T convertFromEntity(final U entity) {
|
||||||
return fromEntity.apply(entity);
|
return fromEntity.apply(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final List<U> createFromDtos(final Collection<T> dtos) {
|
public final List<U> createFromDtos(final Collection<T> dtos) {
|
||||||
return dtos.stream().map(this::convertFromDto).collect(Collectors.toList());
|
return dtos.stream().map(this::convertFromDto).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public final List<T> createFromEntities(final Collection<U> entities) {
|
public final List<T> createFromEntities(final Collection<U> entities) {
|
||||||
return entities.stream().map(this::convertFromEntity).collect(Collectors.toList());
|
return entities.stream().map(this::convertFromEntity).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -64,27 +65,26 @@ The specialized converters inherit from this base class as follows.
|
|||||||
```java
|
```java
|
||||||
public class UserConverter extends Converter<UserDto, User> {
|
public class UserConverter extends Converter<UserDto, User> {
|
||||||
|
|
||||||
public UserConverter() {
|
public UserConverter() {
|
||||||
super(UserConverter::convertToEntity, UserConverter::convertToDto);
|
super(UserConverter::convertToEntity, UserConverter::convertToDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserDto convertToDto(User user) {
|
private static UserDto convertToDto(User user) {
|
||||||
return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId());
|
return new UserDto(user.firstName(), user.lastName(), user.active(), user.userId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static User convertToEntity(UserDto dto) {
|
|
||||||
return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private static User convertToEntity(UserDto dto) {
|
||||||
|
return new User(dto.firstName(), dto.lastName(), dto.active(), dto.email());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now mapping between `User` and `UserDto` becomes trivial.
|
Now mapping between `User` and `UserDto` becomes trivial.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var userConverter = new UserConverter();
|
var userConverter=new UserConverter();
|
||||||
var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com");
|
var dtoUser=new UserDto("John","Doe",true,"whatever[at]wherever.com");
|
||||||
var user = userConverter.convertFromDto(dtoUser);
|
var user=userConverter.convertFromDto(dtoUser);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@@ -95,11 +95,37 @@ var user = userConverter.convertFromDto(dtoUser);
|
|||||||
|
|
||||||
Use the Converter Pattern in the following situations:
|
Use the Converter Pattern in the following situations:
|
||||||
|
|
||||||
* When you have types that logically correspond with each other and you need to convert entities
|
* When there are types that logically correspond with each other, and there is a need to convert between them.
|
||||||
between them.
|
* In applications that interact with external systems or services that require data in a specific format.
|
||||||
* When you want to provide different ways of types conversions depending on the context.
|
* For legacy systems integration where data models differ significantly from newer systems.
|
||||||
* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the
|
* When aiming to encapsulate conversion logic to promote single responsibility and cleaner code.
|
||||||
domain equivalence.
|
|
||||||
|
## Known Uses
|
||||||
|
|
||||||
|
* Data Transfer Objects (DTOs) conversions in multi-layered applications.
|
||||||
|
* Adapting third-party data structures or API responses to internal models.
|
||||||
|
* ORM (Object-Relational Mapping) frameworks for mapping between database records and domain objects.
|
||||||
|
* Microservices architecture for data exchange between different services.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Separation of Concerns: Encapsulates conversion logic in a single component, keeping the rest of the application unaware of the conversion details.
|
||||||
|
* Reusability: Converter components can be reused across the application or even in different applications.
|
||||||
|
* Flexibility: Makes it easy to add new conversions without impacting existing code, adhering to the [Open/Closed Principle](https://java-design-patterns.com/principles/#open-closed-principle).
|
||||||
|
* Interoperability: Facilitates communication between different systems or application layers by translating data formats.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
|
||||||
|
* Overhead: Introducing converters can add complexity and potential performance overhead, especially in systems with numerous data formats.
|
||||||
|
* Duplication: There's a risk of duplicating model definitions if not carefully managed, leading to increased maintenance.
|
||||||
|
|
||||||
|
## Related Patterns
|
||||||
|
|
||||||
|
* [Adapter](https://java-design-patterns.com/patterns/adapter/): Similar in intent to adapting interfaces, but Converter focuses on data models.
|
||||||
|
* [Facade](https://java-design-patterns.com/patterns/facade/): Provides a simplified interface to a complex system, which might involve data conversion.
|
||||||
|
* [Strategy](https://java-design-patterns.com/patterns/strategy/): Converters can use different strategies for conversion, especially when multiple formats are involved.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|||||||
@@ -24,21 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.iluwatar.converter;
|
package com.iluwatar.converter;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User class.
|
* User record.
|
||||||
*/
|
*/
|
||||||
@ToString
|
public record User(String firstName, String lastName, boolean active, String userId) {}
|
||||||
@EqualsAndHashCode
|
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class User {
|
|
||||||
private final String firstName;
|
|
||||||
private final String lastName;
|
|
||||||
private final boolean active;
|
|
||||||
private final String userId;
|
|
||||||
}
|
|
||||||
@@ -34,11 +34,10 @@ public class UserConverter extends Converter<UserDto, User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static UserDto convertToDto(User user) {
|
private static UserDto convertToDto(User user) {
|
||||||
return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId());
|
return new UserDto(user.firstName(), user.lastName(), user.active(), user.userId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static User convertToEntity(UserDto dto) {
|
private static User convertToEntity(UserDto dto) {
|
||||||
return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail());
|
return new User(dto.firstName(), dto.lastName(), dto.active(), dto.email());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -24,23 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.iluwatar.converter;
|
package com.iluwatar.converter;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User DTO class.
|
* UserDto record.
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
public record UserDto(String firstName, String lastName, boolean active, String email) {}
|
||||||
@Getter
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@ToString
|
|
||||||
public class UserDto {
|
|
||||||
|
|
||||||
private final String firstName;
|
|
||||||
private final String lastName;
|
|
||||||
private final boolean active;
|
|
||||||
private final String email;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,6 @@ class AppTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue: Add at least one assertion to this test case.
|
* Issue: Add at least one assertion to this test case.
|
||||||
*
|
|
||||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||||
* throws an exception.
|
* throws an exception.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -65,20 +65,20 @@ class ConverterTest {
|
|||||||
void testCustomConverter() {
|
void testCustomConverter() {
|
||||||
var converter = new Converter<UserDto, User>(
|
var converter = new Converter<UserDto, User>(
|
||||||
userDto -> new User(
|
userDto -> new User(
|
||||||
userDto.getFirstName(),
|
userDto.firstName(),
|
||||||
userDto.getLastName(),
|
userDto.lastName(),
|
||||||
userDto.isActive(),
|
userDto.active(),
|
||||||
String.valueOf(new Random().nextInt())
|
String.valueOf(new Random().nextInt())
|
||||||
),
|
),
|
||||||
user -> new UserDto(
|
user -> new UserDto(
|
||||||
user.getFirstName(),
|
user.firstName(),
|
||||||
user.getLastName(),
|
user.lastName(),
|
||||||
user.isActive(),
|
user.active(),
|
||||||
user.getFirstName().toLowerCase() + user.getLastName().toLowerCase() + "@whatever.com")
|
user.firstName().toLowerCase() + user.lastName().toLowerCase() + "@whatever.com")
|
||||||
);
|
);
|
||||||
var u1 = new User("John", "Doe", false, "12324");
|
var u1 = new User("John", "Doe", false, "12324");
|
||||||
var userDto = converter.convertFromEntity(u1);
|
var userDto = converter.convertFromEntity(u1);
|
||||||
assertEquals("johndoe@whatever.com", userDto.getEmail());
|
assertEquals("johndoe@whatever.com", userDto.email());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user