본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로프로그래밍 › Java › 기초

규칙 2. 생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라

목차

규칙 2. 생성자 인자가 많으면 Builder를 고려하기

필수 값 두 개와 선택 값 여러 개를 한 생성자에 순서대로 넣으면 new NutritionFacts(240, 9, 100, 0, 25, 12)의 숫자가 각각 무엇을 뜻하는지 읽기 어렵다. 매 선택 값 조합마다 생성자를 늘리는 점층적 생성자 방식은 중복도 생긴다. 반대로 기본 생성자 뒤에 setter를 여러 번 호출하면 필수 값이 빠진 중간 상태를 만들 수 있다. Builder는 필수 값은 처음에, 선택 값은 이름 붙은 메서드로, 검증은 build()에서 처리한다.

완성 예제

public final class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int sodium;

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

    public static final class Builder {
        private final int servingSize;
        private final int servings;
        private int calories;
        private int sodium;

        public Builder(int servingSize, int servings) {
            if (servingSize <= 0 || servings <= 0)
                throw new IllegalArgumentException("필수 수량은 양수여야 합니다");
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int value) {
            if (value < 0) throw new IllegalArgumentException("열량은 0 이상이어야 합니다");
            this.calories = value;
            return this;
        }

        public Builder sodium(int value) {
            if (value < 0) throw new IllegalArgumentException("나트륨은 0 이상이어야 합니다");
            this.sodium = value;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    public int calories() { return calories; }
    public int sodium() { return sodium; }
}
NutritionFacts facts = new NutritionFacts.Builder(240, 9)
    .calories(100)
    .sodium(25)
    .build();
System.out.println(facts.calories()); // 100

Builder(240, 9)에서 필수 수량을 확인한다. 선택 값은 기본 0이고, setter처럼 보이는 메서드가 빌더 자신을 반환해 호출을 이어 붙인다. build()는 NutritionFacts의 final 필드로 값을 복사한다. 이 예제의 완성 객체는 정수 필드만 갖기 때문에 외부에서 내용을 바꿀 경로가 없다. 목록 같은 가변 값이 들어오면 빌더로 받을 때 또는 완성 객체를 만들 때 방어적으로 복사해야 한다.

중간 상태와 재사용 정책

빌더 자체는 변경 가능한 객체다. Builder b = ...; NutritionFacts a = b.build(); b.calories(200); NutritionFacts c = b.build();를 실행하면 a는 100, c는 200 같은 서로 다른 완성 객체를 만든다. 이 동작을 허용할지, build() 뒤 빌더를 다시 쓰지 못하게 할지는 API 계약이다. 여러 스레드가 같은 빌더를 동시에 바꾸는 것은 별도 동기화가 없다면 안전하지 않다.

필수 값 사이에 sodium <= ... 같은 교차 조건이 있다면 개별 설정 메서드만으로 검사할 수 없으므로 build()에서도 검증한다. 잘못된 객체가 밖으로 나간 뒤에만 실패하는 것보다 완성 시점에 한 번 확인하는 편이 명확하다.

상황고려할 방법
필수 인자가 한두 개, 선택 값이 거의 없음생성자 또는 정적 팩터리
선택 값이 많고 같은 타입이 반복됨Builder
일부 필드를 수정하며 재사용하는 내부 설정 객체가변 객체, 단 중간 상태 관리
단순 데이터 전달, 변경 불필요record와 검증 생성자도 고려

빌더는 코드가 길어지고 완성 전에 별도 객체 하나를 만든다. 모든 2~3개 인자 객체에 습관적으로 적용하기보다 호출 코드의 가독성과 불변 조건의 수를 기준으로 정한다. 원문의 점층적 생성자 예제에는 타입이 빠진 sodium 인자와 sodifum 오타가 있어 컴파일되지 않았으므로 완성 가능한 빌더 예제로 바꿨다.

참고: Java 언어 명세: Class Instance Creation, Oracle Records

같은 카테고리의 글