Language/Design Pattern

빌더 패턴

소프 2020. 12. 29.

빌더패턴이란

클라이언트 코드에서 필요한 객체를 직접 생성하는 대신, 그 전에 필수 인자들을 전달하여 빌더 객체를 만든 뒤, 빌더 객체에 정의된 설정 메서드들을 호출하여 인스턴스를 생성하는 방식이다.

빌더패턴으로 해결할 수 있는점

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/

jdm.kr/blog/217

asfirstalways.tistory.com/350

스터디 회원님 퀴즈

댓글