Disclaimer: The content of this blog are my views and understanding of the topic. I do not intend to demean anything or anyone. I am trying to share my views on the topic so that you will have a different thought process and perspective to look at this topic.
We have seen in the Advanced Builder Design Blog, the process of creating a Builder Design Pattern in Java using the latest language features. Inheritance in Java is a useful tool for reusing modelled classes and effectively defining class hierarchies. In the programming world, Java inheritance can be implemented based on the use case. For instance, consider the need to create separate DTO classes for capturing Create and Update requests. Let's analyze this requirement.
Use Case
We have a CreateUserDto class that captures the user's name attribute and includes a builder for constructing it.
( Note : We used a simple model for focusing on the main topic. Hence do not get confused why we are using Builder for class with single field)
package org.example.first;
import java.util.function.Consumer;
public class CreateUserDto {
private final String name;
public CreateUserDto(Builder builder) {
this.name = builder.name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "CreateUserDto{" +
"name='" + name + '\'' +
'}';
}
public static class Builder {
private String name;
private Builder() {
}
public static CreateUserDto with(Consumer<Builder> builderConsumer) {
Builder builder = new Builder();
builderConsumer.accept(builder);
return builder.build();
}
private CreateUserDto build() {
return new CreateUserDto(this);
}
public void setName(String name) {
this.name = name;
}
}
}
We now want to create an UpdateUserDto that includes an additional field, 'id', for unique identification of the user in the database when updating their name.
Few notes on the id field:
The id field is generated after user creation, hence it cannot be added in the CreateUserDto, but it is required for Update request.
We don't want to model the same attributes again in the case of UpdateUserDto since only those fields which are available during create could be updated.
Hence one possible way to model is below:
package org.example.first;
import java.util.function.Consumer;
public class UpdateUserDto extends CreateUserDto {
private final Integer id;
public UpdateUserDto(Builder builder, CreateUserDto.Builder createUserDtoBuilder) {
super(createUserDtoBuilder);
this.id = builder.id;
}
public Integer getId() {
return id;
}
@Override
public String toString() {
return "UpdateUserDto{" +
"id=" + id +
"} " + super.toString();
}
public static class Builder {
private final Integer id;
private CreateUserDto.Builder createUserDtoBuilder;
private Builder(Integer id) {
this.id = id;
}
public static UpdateUserDto with(Integer id, Consumer<CreateUserDto.Builder> builderConsumer) {
Builder builder = new Builder(id);
CreateUserDto.Builder.with(createUserDtoBuilder -> {
builderConsumer.accept(createUserDtoBuilder);
builder.setCreateUserDtoBuilder(createUserDtoBuilder);
});
return builder.build();
}
public void setCreateUserDtoBuilder(CreateUserDto.Builder createUserDtoBuilder) {
this.createUserDtoBuilder = createUserDtoBuilder;
}
private UpdateUserDto build() {
return new UpdateUserDto(this, createUserDtoBuilder);
}
}
}
Code Analysis
Below are the key points to be noted in the model:
The Builder has an "id" field as a constructor parameter and it is marked as final, making it a mandatory field for update scenarios.
"With" method takes a Consumer for the CreateUserDto.Builder, as the fields to be built are in the parent class builder.
The instance of the CreateUserDto.Builder is captured using a setter method, "setCreateUserDtoBuilder".
The captured "createUserDtoBuilder" is used to pass it to the CreateUserDto class constructor from the child class UpdateUserDto.
Usage Demo Class
Below is a demo class to showcase the usage of these builders as per the above models:
package org.example; import org.example.first.CreateUserDto; import org.example.first.UpdateUserDto; public class Main { public static void main(String[] args) { CreateUserDto createUserDto = CreateUserDto.Builder.with($ -> { $.setName("John Doe"); }); UpdateUserDto updateUserDto = UpdateUserDto.Builder.with(100, $ -> { $.setName("Jane Doe"); }); System.out.println(createUserDto); System.out.println(updateUserDto); } }
Sample Output
CreateUserDto{name='John Doe'}
UpdateUserDto{id=100} CreateUserDto{name='Jane Doe'}
Summary
We have achieved the reusability of the modelled field from the parent class with a Builder. Additionally, we have a builder for the child class. Modeling classes using this approach may seem overwhelming at first, but if you are well-versed in the Java programming language and its features, then the code is straightforward. It is important to remember that although duplication by copy-pasting may seem easy to understand and model, it can become challenging to maintain the code and understand the impact of changes in the future. Therefore, we should strive for static code readability as much as possible.
I'd love to hear your opinions - reach out to me at biz.info@vikasietum.com
Comments