@Builder class 선언 vs constructor 선언

Sera Lee·2022년 2월 28일
6

Lombok 잘 사용하기

목록 보기
1/2

Builder 패턴은 언제 사용하는게 좋을까?

  • 선택적 매개변수가 많을 때
    • 매개변수가 많고 게다가 선택적 매개변수가 많을 때 constructor 를 사용하려면 선택적매개변수가 각기 다른 생성자를 만들어줘야하는데, 매개변수가 많은 경우에는 정의해 줘야하는 생성자가 굉장히 많아진다.

Builder 패턴 예제 코드

  • 다음은 effectve java 아이템2 에 정의된 빌드패턴이다.
    • 빌드패턴은 다음을 만족해야한다.
      • class 내부에 static class Builder 가 정의된다.

      • Builder 클래스 내부에는 class의 매개변수들이 선언되는데 필수 매개변수는 final이 선언되어야 하고, 선택 매개변수는 기본값을 설정한다.

      • Builder 생성자에는 필수 매개변수들이 파라미터로 들어가게 된다.

        public Builder(servingSize, servings) {}
      • 선택 매개변수들은 method 이름으로 하나씩 정의한다.

        public Builder calories(int val) {
            this.calories = val;
            return this;
        }
        
        public Builder fat(int val) {
             this.fat = val;
             return this;
        }
      • 클래스를 리턴하는 build() 함수를 정의한다.

        public NutritionFacts build() {
             return new NutritionFacts(this);
        }
      • class 에 Builder 를 파라미터로 받는 생성자를 작성한다.

        public NutritionFacts(Builder builder) {
           servingSize = builder.servingSize;
           servings = builder.servings;
           calories = builder.calories;
           fat = builder.fat;
           sodium = builder.sodium;
           carbohydrate = builder.carbohydrate;
        }
  • 작성된 코드는 다음과 같다.

public class NutritionFacts {

    private final int servingSize; // 필수 매개변수
    private final int servings;    // 필수 매개변수
    private final int calories;    // 선택 매개변수
    private final int fat;         // 선택 매개변수

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
    }

    public static class Builder {

        private final int servingSize; // 필수 매개변수는 반드시 final 을 선언한다.
        private final int servings;    // 필수 매개변수는 반드시 final 을 선언한다.

        // 선택 매개변수는 기본값으로 설정한다.
        private int calories = 0;    // 선택 매개변수
        private int fat = 0;         // 선택 매개변수

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            this.calories = val;
            return this;
        }

        public Builder fat(int val) {
            this.fat = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
}
  • 이 클래스를 사용하는 클라이언트 코드
// 이 경우는 필수값 servingSize, servings 중 하나의 값이 비었으므로 컴파일에러가 난다.
new NutritionFacts.Builder(10).fat(1000).calories(1220).build(); 

// 옵셔널 한 값을 필수로 셋팅하지 않아도 된다.
new NutritionFacts.Builder(10, 10).build();

// 옵셔널 한 값은 다음처럼 사용될 수 있다
new NutritionFacts.Builder(10, 10).fat(1000).calories(1220).build();
new NutritionFacts.Builder(10, 10).fat(10000).build();

@Builder 를 사용하기

  • @Builder 를 사용하는 방법은 class 위에 선언하는 방법, constructor에 선언하는 방법이 있다.

@Builder를 class 위에 선언하기

  • compile 후 generate 된 NutritionFacts.class
NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
	this.servingSize = servingSize;
  this.servings = servings;
  this.calories = calories;
  this.fat = fat;
}

  public static NutritionFactsBuilder builder() {
	  return new NutritionFactsBuilder();
  }
}
  • 문제점
    • 모든 매개변수가 생성자의 파람으로 들어가게 된다.
    • 객체 생성 시 받지 않아야 할 매개변수들도 빌더에 노출이 된다.
    • 이를 해결 하기 위해 constructor 위에 @Builder 를 선언한다.

@Builder 를 constructor 위에 선언하기

  • compile 후 generate 된 NutritionFacts.class
public class NutritionFacts {
    private int servingSize;
    private int servings;
    private int calories;
    private int fat;
  
    public NutritionFacts(int servingSize, int servings) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = 0;
        this.fat = 0;
    }

    public static NutritionFacts.NutritionFactsBuilder builder() {
        return new NutritionFacts.NutritionFactsBuilder();
    }

    public static class NutritionFactsBuilder {
        private int servingSize;
        private int servings;
        private int calories;
        private int fat;

        NutritionFactsBuilder() {
        }

        public NutritionFacts.NutritionFactsBuilder servingSize(int servingSize) {
            this.servingSize = servingSize;
            return this;
        }

        public NutritionFacts.NutritionFactsBuilder servings(int servings) {
            this.servings = servings;
            return this;
        }

        public NutritionFacts.NutritionFactsBuilder calories(int calories) {
            this.calories = calories;
            return this;
        }

        public NutritionFacts.NutritionFactsBuilder fat(int fat) {
            this.fat = fat;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this.servingSize, this.servings, this.calories, this.fat);
        }

        public String toString() {
            return "NutritionFacts.NutritionFactsBuilder(servingSize=" + this.servingSize + ", servings=" + this.servings + ", calories=" + this.calories + ", fat=" + this.fat +")";
        }
    }
}
  • class 선언 시 문제가 되었던 객체 생성 시 받지 않아야 할 매개변수들도 빌더에 노출이 되는 문제점을 해결할 수 있다.
  • 선택적 매개변수인 calories, fat 는 Builder에 노출이 되지 않는 것을 확인할 수 있다.
  • 하지만 테스트코드를 짤때, 귀찮아진다.
    • 만약, calories 를 초기값으로 세팅을 하고자 할 때 Builder 를 통해서는 제공이 안되므로 calories 를 @Setter 를 제공해서 다음과 같이 NutrionFacts를 생성한 후 calories 를 set 할 수 있다.

      NutritionFacts nutritionFacts = NutritionFacts.builder.servings(10).servingSize(1).build();
      nutritionFacts.setCalories(1000);
    • 부득이하게 @Setter 를 선언하게 될 수 밖에 없고,

      • 객체의 안전성이 보장받기 힘들어진다.
      • 특히 엔티티에서는 @Setter를 사용 시 해당 업데이트 문이 언제 발생했는지 추적하기가 어렵다.
  • Builder에서도 선택적 매개변수를 세팅할 수 있도록 우아하게 @Builder를 사용할 수 있는 방법이 없을까?

💎빌더패턴의 이점을 살려(필수매개변수,선택매개변수) @Builder 사용하기

@Builder(builderMethodName = "")을 사용한다.

@Builder(builderMethodName = "innerBuilder")
public class NutritionFacts {

    private final int servingSize; // 필수 매개변수
    private final int servings;    // 필수 매개변수
    @Builder.Default private int calories = 0;    // 선택 매개변수
    @Builder.Default private int fat = 0;         // 선택 매개변수

    public static NutritionFactsBuilder builder(int servingSize, int servings) {
        return innerBuilder().servings(servings).servingSize(servingSize);
    }
}
  • builder 함수를 쓰기 위해서는 무조건 servingSize, servings 를 주입 받아야 하는 custom method 를 선언을 해두면, 처음에 선언한 innerBuilder() 를 통해서 객체를 만들고, 다음에 롬복에서 제공하는 builder 패턴을 사용하겠다는 의미이다.
  • @Builder.Default 값이 할당 되지 않은 경우 초기값을 설정하겠다는 뜻이다.
  • compile 후 generate 된 NutritionFacts.class
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private int calories;
    private int fat;

    public static NutritionFactsBuilder builder(int servingSize, int servings) {
        return innerBuilder().servings(servings).servingSize(servingSize);
    }

    private static int $default$calories() {
        return 0;
    }

    private static int $default$fat() {
        return 0;
    }

    NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
    }

    public static NutritionFactsBuilder innerBuilder() {
        return new NutritionFactsBuilder();
    }
}
  • 이 클래스를 사용하는 클라이언트 코드는 다음과 같다.
NutritionFacts nutritionFacts = NutritionFacts.builder(10, 200).fat(130).calories(222).build();

1개의 댓글

comment-user-thumbnail
2024년 5월 13일

When comparing the Builder class and constructor in object-oriented programming, it's essential to understand their distinct roles and advantages. While constructors initialize objects directly, the Builder class offers a more flexible approach, particularly useful when dealing with complex object creation scenarios. With the Builder pattern, you can set optional parameters and create objects step by step, enhancing readability and maintainability. This flexibility becomes especially valuable when dealing with objects requiring numerous configuration options. Additionally, the Builder pattern promotes code reuse and simplifies the process of creating objects with varying configurations. If you're interested in exploring this topic further, you can find insightful resources on object-oriented design patterns at https://writepaper.com/custom-essay.

답글 달기