자바 디자인 패턴 용어 사전
생성 패턴빌더 · 점층적 생성자

Builder

복잡한 객체를 단계적으로 조립하는 패턴. 생성자 인자가 많고 선택적일 때 쓴다.

복잡한 객체를 단계적으로 조립하는 패턴.

무엇을 푸나 — 점층적 생성자의 지옥

// ❌ 인자가 늘어날수록 생성자가 폭발한다
new Pizza(12, true, false, true, false, true);   // 무엇이 무엇인지 알 수 없다

new Pizza(12);
new Pizza(12, true);
new Pizza(12, true, false);   // 조합마다 오버로드

빌더로

Pizza pizza = new Pizza.Builder(12)
    .cheese(true)
    .pepperoni(true)
    .build();                  // 여기서 필수값 검증도 한다
public class Pizza {
    private final int size;
    private final boolean cheese;

    private Pizza(Builder b) { this.size = b.size; this.cheese = b.cheese; }

    public static class Builder {
        private final int size;              // 필수는 생성자로
        private boolean cheese = false;      // 선택은 기본값으로

        public Builder(int size) { this.size = size; }
        public Builder cheese(boolean v) { this.cheese = v; return this; }
        public Pizza build() {
            if (size < 8) throw new IllegalArgumentException("너무 작다");
            return new Pizza(this);
        }
    }
}

필수는 빌더 생성자로, 선택은 메서드로 나누는 것이 관용구다.

얻는 것

  • 이름이 있어 무슨 값인지 읽힌다
  • 불변 객체를 만들 수 있다 (모든 필드 final)
  • build() 에서 한 번에 검증한다 (객체가 반쯤 만들어진 상태로 존재하지 않는다)
  • 순서에 자유롭다

언어가 이미 지원하기도 한다

  • 명명 인자 + 기본 인자가 있는 언어에서는 빌더가 필요 없는 경우가 많다

  • 자바에서도 record + 정적 팩토리로 충분한 경우가 있다

  • 필드가 4~5개를 넘고 선택적인 것이 많을 때가 빌더의 자리다

Prototype — 복제로 만든다

// 생성 비용이 크거나 현재 상태를 그대로 복제해야 할 때
GameObject clone = original.clone();

주의: 얕은 복사와 깊은 복사를 구분해야 한다

  • 얕은 복사면 내부 객체를 공유해 한쪽 수정이 다른 쪽에 보인다

자바의 Cloneable 은 설계가 나쁘기로 유명하다

  • 복사 생성자나 정적 팩토리(copyOf)를 쓰는 편이 낫다

면접 함정

  • "빌더는 setter의 다른 형태" → setter는 객체를 가변으로 만들고, 빌더는 불변 객체를 만드는 수단이다.
  • "빌더를 쓰면 항상 좋다" → 필드가 두세 개면 코드만 늘어난다.

롬복·코틀린에서는

@Builder                      // 롬복이 빌더를 생성해 준다
public class Pizza { private final int size; private final boolean cheese; }

Pizza.builder().size(12).cheese(true).build();

그런데 롬복 @Builder 는 필수값 검증이 없다

  • 아무것도 안 넣고 build() 해도 통과한다
  • @NonNull 을 붙이거나 커스텀 build() 를 두어야 한다

"코드를 줄여 주는 도구" 가 "안전성" 까지 주지는 않는다

언어 기능이 대체하는 경우

// 명명 인자 + 기본값이 있으면 빌더가 거의 필요 없다
data class Pizza(
    val size: Int,
    val cheese: Boolean = false,
    val pepperoni: Boolean = false
)
Pizza(size = 12, cheese = true)

빌더 패턴이 필요한 이유의 상당 부분이 "자바에 명명 인자가 없어서" 였다 — 언어가 개선되면 패턴이 사라지는 사례다.

단계가 강제되는 빌더

// 타입으로 순서를 강제할 수도 있다 (step builder)
interface SizeStep { ToppingStep size(int s); }
interface ToppingStep { BuildStep cheese(boolean b); }
// → size 를 안 넣으면 애초에 build() 에 도달할 수 없다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 생성 패턴 — 객체를 만드는 일을 떼어낸다