빌더패턴이란
클라이언트 코드에서 필요한 객체를 직접 생성하는 대신, 그 전에 필수 인자들을 전달하여 빌더 객체를 만든 뒤, 빌더 객체에 정의된 설정 메서드들을 호출하여 인스턴스를 생성하는 방식이다.
빌더패턴으로 해결할 수 있는점
1. 생성자 오버로딩를 하지 않아도 원하는 데이터만 주입하여 객체를 만들 수 있다.
2. 데이터의 순서에 상관없이 객체를 만들어 낸다.
3. 객체 생성시 생성자에 null값을 넣어주지 않아 코드의 가독성이 좋아진다.
이펙티브 자바 스타일 빌더 패턴
package test;
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);
}
} //builder 패턴 끝
private NutritionFacts(Builder builder) {
/**
* 왼쪽에 할당되는 변수들은 NutritionFacts클래스의 인스턴스 변수들
*/
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
public static void main(String[] args) {
Person person = new Person.Builder(1,1)
.calories(1)
.fat(1)
.build();
}
- public NutritionsFacts build() 메소드에서 new NutritionFacts(this)를 호출하면 NutritionFacts 클래스의 생성자인 private NutritionFacts(Builder builder)를 호출한다
- Builder 클래스에서 할당받지 않은 값은 디폴트 값, 할당받은 값은 받은 값으로 NutritionFacts의 인스턴 스 변수를 초기화해준다.
※ lombok의 @Builder는 위의 패턴으로 구성되어 있다.
주의사항
- Optional parameters 파라미터 에서 기본값으로 초기화 되는 것에 주의하자.
- 빌더 패턴 사용시 엔티티에서 참조형 클래스를 초기화를 했더라도 클라이언트에서 값을 할당받지
못하면 null값이 된다.
Ex) 예제 코드
@Entity
@Builder
public class Member {
@Id @GeneratedValue
@Column(name = "account_id")
private Long id;
private String nickname;
private String email;
private String password;
@ManyToMany
private Set<Tag> tags = new HashSet<>(); // 엔티티에서 초기화
}
@Entity
@Builder
public class Tag {
@Id @GeneratedValue
@Column(name = "tag_id")
private Long id;
@Column(unique = true)
private String title;
}
//테스트케이스
void test(){
...
Member member = Member.builder() //tags 미사용
.nickname(signUpForm.getNickname())
.email(signUpForm.getEmail())
.password(passwordEncoder.encode(signUpForm.getPassword()))
.build();
em.persist(member);
member.getTags.add(tag); //NPE 발생
...
}
- new NutritionFacts.Builder() 말고 NutritionFacts.Builder()로 하는 방법
=> NutritionFacts 클래스 안에 Builder 클래스르 반환하는 메소드를 만들어주면 된다.
class NutritionFacts {
...
public static Builder builder(){
return new Builder(1,1);
}
...
public static class Builder {
//클라이언트 코드
NutritionFacts nutritionFacts = NutritionFacts.builder()
.calories(1)
.sodium(1)
.build();
참고
johngrib.github.io/wiki/builder-pattern/
스터디 회원님 퀴즈