생성자에 인자가 많을 때는 빌더 패턴을 고려하라
-Effective Java 규칙 2 - 조슈아 블로크-
빌더 패턴은 복잡한 객체를 생성하는 방법을 정의하는 클래스와 표현하는 방법을 정의하는 클래스를 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공하는 패턴입니다.
빌더 패턴은 생성 패턴(Creational Pattern) 중 하나이다. 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴입니다. 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해줍니다.
public class ProductDto {
private int displayInfoId;
private String placeName;
private String productContent;
private String productDescription;
private int productId;
private String productImageUrl;
public ProductDto() {
}
public ProductDto(int displayInfoId, String placeName, String productContent, String productDescription,
int productId, String productImageUrl) {
this.displayInfoId = displayInfoId;
this.placeName = placeName;
this.productContent = productContent;
this.productDescription = productDescription;
this.productId = productId;
this.productImageUrl = productImageUrl;
}
...
}
위는 실제 프로젝트에서 사용된 코드이다.
속성들이 추가될 때마다 넘겨줘야하는 인자 값들이 계속 늘어나게 된다. 또한 필수가 아닌 값들은 null로 주게되면 무엇이 인자로 들어가는지 정확히 이해하기 어렵다. 또 데이터를 입력하는 순서를 항상 기억해야한다는 부담감이 있다.
new ProductDto(1, null, null, null, 2, null);
또한 지금대로라면 들어오는 인자의 개수가 다를 때마다 생성자를 추가해줘야 한다.
즉 장점은 아래와 같다.
- 필요한 데이터만 설정할 수 있음
- 유연성을 확보할 수 있음
- 가독성을 높일 수 있음
- 불변성을 확보할 수 있음
출처: https://mangkyu.tistory.com/163 [MangKyu's Diary]
// Effective Java의 Builder Pattern
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters(필수 인자)
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this; // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
위와 같이 하면 다음과 같이 객체를 생성할 수 있다
NutritionFacts.Builder builder = new NutritionFacts.Builder(240, 8);
builder.calories(100);
builder.sodium(35);
builder.carbohydrate(27);
NutritionFacts cocaCola = builder.build();
또는 다음과 같이 사용할 수도 있다.
// 각 줄마다 builder를 타이핑하지 않아도 되어 편리하다.
NutritionFacts cocaCola = new NutritionFacts
.Builder(240, 8) // 필수값 입력
.calories(100)
.sodium(35)
.carbohydrate(27)
.build(); // build() 가 객체를 생성해 돌려준다.
- 각 인자가 어떤 의미인지 알기 쉽다.
- setter 메소드가 없으므로 변경 불가능 객체를 만들 수 있다.
- 한 번에 객체를 생성하므로 객체 일관성이 깨지지 않는다.
- build() 함수가 잘못된 값이 입력되었는지 검증하게 할 수도 있다.
출처: https://johngrib.github.io/wiki/builder-pattern/