본문 바로가기

Java

자바 빌더(Builder) 패턴 + lombok

자바에서 객체를 생성할 때 일반적으로 생성자를 이용한다. 그런데, 가만히 보면 프로그래머가 생성자의 순서를 외워서 인스턴스를 만들어야 한다. 그런데.. 몇 가지 문제점이 있다.

1. 타입이 같은 파라미터를 헷갈릴 수 있다.

public Order(int id, int UUid, String orderDetail, String Order) //id, UUid .. 그 다음이 뭐였더라?
{
....
}

Order 객체를 생성하기 위해 생성자에서 값을 넣는 도중 순서가 생각이 안난다면, 클래스를 직접 보거나 IDE에서 마우스를 갖다 대야 볼 수 있다.

 

2. 생성자의 파라미터가 많으면 다 넣어줘야 한다.

public Order(int id, int UUID, String name, String OrderDetail ....) //파라미터가 10개, 20개라면!?
{
....
}

파라미터가 많으면, 외우기도 힘들고 쓰기도 귀찮다.

 

생성자에 값을 명시적으로 넣을 수는 없을까?

위와 같은 질문에 착안하여 만들어진게 빌더 패턴이다.

빌더 (Builder) 직역 그대로 무언가를 만드는 역할이다.

// 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 cocaCola = new NutritionFacts
    .Builder(240, 8)    // 필수값 입력
    .calories(100)
    .sodium(35)
    .carbohydrate(27)
    .build();           // build() 가 객체를 생성해 돌려준다.

객체에 어떤 값이 들어가는지 눈에 확 보인다. 또한 setter 메서드를 만들지 않을 수 있기 때문에 객체를 한 번 만들고 값이 변경되지 않아야 되는 상황 (e.g. 엔티티 클래스)에적합하다.

 

그런데, 롬복에서 이러한 빌더 패턴을 지원해주는 @Builder 애노테이션이 있다. 사용법은 다음과 같다.

@Builder
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;
}

호출하는 방법도 달라지지 않는다. 타이핑을 획기적으로 줄일 수 있는 롬복을 사용하길 적극 추천한다.