Item - 2 Effective Java Consider a Builder When Faced with Many Constructor Parameters

Berat Yesbek
4 min readAug 10, 2023

--

In this writing, we will discuss item 2 in Effective JAVA recommended by Joshua Bloch.

Static factories and constructors have limitations. They do not have scalability to large numbers of optional parameters. Consider the Product class that has many parameters such as id, name, description, type, images, price, quantity, category, and so on. Some fields in the Product class must be mandatory some of them must be optional. You can provide this feature with a traditional constructor and encapsulate. You can create a constructor block that takes mandatory fields and you can utilize getter/setter methods for optional fields. What if you have many mandatory parameters and so many different cases? Probably, you have to create constructor more than one. This approach might diminish readability and clarity

Product.java

import java.math.BigDecimal;
import java.time.OffsetDateTime;

public class Product {

private int ID;
private String name;
private String description;
private String type;
private String[] images;
private BigDecimal price;
private int quantity;
private int categoryId;
private int sellerId;
private OffsetDateTime createdAt;

public Product(int ID, String name, String description, String type, String[] images, BigDecimal price, int quantity, int categoryId, int sellerId, OffsetDateTime createdAt) {
this.ID = ID;
this.name = name;
this.description = description;
this.type = type;
this.images = images;
this.price = price;
this.quantity = quantity;
this.categoryId = categoryId;
this.sellerId = sellerId;
this.createdAt = createdAt;
}

public Product(int id, OffsetDateTime createdAt) {
this.ID = id;
this.createdAt = createdAt;
}

public int getID() {
return ID;
}

public void setID(int ID) {
this.ID = ID;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

// Some other getter/setter methods ....

}

Test.java

import java.math.BigDecimal;
import java.time.OffsetDateTime;

public class Test {
public static void main(String[] args) {
Product product =
new Product(1,
"Iphone 14 Pro MAX",
"Amazing Phone",
"PHONE",new String[]{},
BigDecimal.valueOf(1200),
100,
43,
31, OffsetDateTime.now());
}
}

As you can see, when a client wants to create an instance from Product must pass the whole parameters to the constructor. We can solve this problem to declare more constructor blocks in the Product class. Can this solution be better? Absolutely not. It cannot be a good solution. There is a better way to solve this problem. Builder Design Patterns can solve this problem.

Builder Pattern

The builder design pattern is under the category of creational design pattern. This pattern was formalized and released by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal book “Design Patterns: Elements of Reusable Object-Oriented Software,” published in 1994. [2]

Product.java

package item2.v2;

import java.math.BigDecimal;
import java.time.OffsetDateTime;

public class Product {

private int ID;
private String name;
private String description;
private String type;
private String[] images;
private BigDecimal price;
private int quantity;
private int categoryId;
private int sellerId;
private OffsetDateTime createdAt;

private Product(ProductBuilder builder) {
this.categoryId = builder.categoryId;
this.description = builder.description;
this.createdAt = builder.createdAt;
this.ID = builder.id;
this.images = builder.images;
this.sellerId = builder.sellerId;
this.price = builder.price;
this.quantity = builder.quantity;
this.name = builder.name;
this.type = builder.type;
}


public static class ProductBuilder {

private int id;
private String description;
private String[] images;
private String name;
private BigDecimal price;
private int quantity;
private int categoryId;
private int sellerId;
private String type;
private OffsetDateTime createdAt;

public ProductBuilder description(String description) {
this.description = description;
return this;
}

public ProductBuilder price(BigDecimal price) {
this.price = price;
return this;
}

public ProductBuilder name(String name) {
this.name = name;
return this;
}

public ProductBuilder quantity(int quantity) {
this.quantity = quantity;
return this;
}

public ProductBuilder categoryId(int categoryId) {
this.categoryId = categoryId;
return this;
}

public ProductBuilder sellerId(int sellerId) {
this.sellerId = sellerId;
return this;
}
public ProductBuilder type(String type) {
this.type = type;
return this;
}
public ProductBuilder createdAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
return this;
}

public ProductBuilder id(int id) {
this.id = id;
return this;
}

public ProductBuilder images(String[] images) {
this.images = images;
return this;
}

public Product build() {
return new Product(this);
}

public static ProductBuilder builder() {
return new ProductBuilder();
}


}
}

Test.java

package item2.v2;

import java.math.BigDecimal;

public class Test {
public static void main(String[] args) {
Product product = Product.ProductBuilder
.builder()
.id(1)
.name("Iphone 14 Pro MAX")
.quantity(100)
.price(BigDecimal.valueOf(1200))
.description("Amazing Smart Phone :)")
.categoryId(43)
.build();
}
}

The Product class contains various attributes that describe the product. These include the product ID, name, description, type, images, price, quantity, category ID, seller ID, and creation time. And constructor block is marked as private that means cannot be invoked directly from outside of the context. An instance of Product is created through the nested ProductBuilder class.

Advantages of Builder Pattern

As you can see builder patterns directly enhance readability, immutability, flexibility, and maintainability.

If you have many optional and mandatory parameters, It can be difficult to understand for developers. Builder pattern directly enhances readability.

You can make your fields immutable which means the fields of the product cannot be modified. This approach helps with thread safety.

If you want to add another parameter in the future, it’s easy to add to the builder without affecting where the object is constructed. (maintainability)

It is looking more complicated than the traditional way I totally understand that. You can use Lombok annotation to get rid of this mess. When you use @Builder annotation on a class, Lombok creates a builder pattern under the hood instead of you. [1]


@Builder
public class Product{

private int id;
private String description;
private String[] images;
private String name;
private BigDecimal price;
private int quantity;
private int categoryId;
private int sellerId;
private String type;
private OffsetDateTime createdAt;

}

Look at this! How good is it? How clear is it? How readable is it? There are no words to describe the power of Lombok. The code is self-explanatory. It is looking fantastic.

Conclusion

Use a builder pattern when you face many constructor parameters. Lombok Project will be a good choice for your projects. Lombok will enhance the readability, maintainability, and clarity of your projects.

References

https://www.baeldung.com/lombok-builder [1]

Bloch, Joshua. Effective Java Programming Third Edition. [2]

Robert C. Martin, Clean Code. [3]

--

--

Berat Yesbek
Berat Yesbek

No responses yet