Java 기초 (빌더 패턴 Builder Pattern)

최병현·2026년 1월 9일

Java

목록 보기
31/38

Builder Pattern(빌더 패턴)

이 내용은 Backend / OOP design in Java 영역에서 “객체 생성(creation)”을 안정적으로 설계하는 대표 패턴이다. 필드가 많아질수록 생성자 기반 생성 방식은 유지보수·가독성·확장성에서 한계가 생기고, 그 문제를 해결하려고 Builder Pattern이 등장했다.


1. 빌더 패턴이 생긴 원인

1) 복잡한 생성자 문제

필드가 많아질수록 생성자의 매개변수도 늘어나고, 생성자 호출부가 “긴 한 줄”이 되어 실수 확률이 급격히 올라간다. 롬복을 써서 생성자 코드는 짧아질 수 있어도, @NonNull / final 같은 제약이 들어가면 기본 생성자 생성 불가 같은 문제가 함께 따라온다.

2) 생성자 오버로딩 폭발

필요한 조합이 달라질 때마다 생성자를 하나씩 늘려야 한다. 예) 예전엔 집전화가 필수 → 지금은 optional. 필드가 optional로 바뀌면 생성자 시그니처도 바꿔야 하고, 기존 호출부도 전부 수정이 필요해진다.

3) 유지보수 악영향

필드 하나 추가/삭제만 해도 AllArgsConstructor 호출부가 전부 깨진다. 메인에서 new 생성자 호출하던 라인들이 전부 오류나면서 수정 범위가 넓어지고, 배포 리스크도 커진다.

4) 가독성 문제

생성자 호출만 보면 “이 값이 어떤 필드인지” 알기 어렵다.

Student student = new Student("김일", "김이");
// "김일"이 name인지 school인지, "김이"가 name인지 school인지 모호함

2. 정의

Builder Pattern은 복잡한 생성자 대신 객체를 단계적으로(체이닝) 생성할 수 있게 해주는 “생성(Creational) 디자인 패턴”이다. GoF(Gang of Four) 디자인 패턴 중 하나이며, 목표는 가독성 + 유연성 + 유지보수성이다.

여기서 유연성은 필드를 순서대로 넣지 않아도 되고, 순서를 바꿔도 정확한 필드에 값이 들어가도록 만든다는 의미다.


3. 특징

  • 필드들을 명시적으로 세팅 가능 (어떤 값이 어떤 필드인지 한눈에 보임)
  • 불필요한 생성자 오버로딩 감소
  • 체이닝 메서드로 객체 생성 흐름이 직관적
  • 필드 추가/변경 시 호출부 영향 최소화

4. 구조 설명

핵심 구조는 3가지다.

  • Outer class: 실제 객체(Person)
  • static inner Builder class: 동일한 필드를 들고, 세터 메서드를 체이닝으로 제공
  • build(): Builder가 모아둔 값을 바탕으로 Outer 객체를 생성해서 반환

5. 핵심 클래스 구현 (수동 Builder)

import lombok.ToString;

@ToString
public class Person {
    private String name;
    private int age;
    private String address;

    // Builder만 통해 생성되도록 생성자를 private로 잠금
    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.address = builder.address;
    }

    public static class Builder {
        private String name;
        private int age;
        private String address;

        public Builder name(String name) {
            this.name = name;
            return this; // chaining
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

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

6. Lombok @Builder 사용

Lombok의 @Builder는 위의 Builder 클래스를 자동 생성해준다. 대신 “자동 생성된 builder() 메서드 + 체이닝 setter + build()” 패턴으로 사용한다.

import lombok.Builder;
import lombok.ToString;

@ToString
@Builder
public class Person2 {
    private String name;
    private int age;
    private String address;
}

7. 실행 예제

public class PersonMain {
    public static void main(String[] args) {

        // 수동 Builder: 순서 상관없이 세팅 가능
        Person person1 = new Person.Builder()
                .age(21)
                .address("부산광역시 부산진구")
                .name("김일")
                .build();
        System.out.println(person1);

        // Builder를 사용하면 "NoArgsConstructor 같은 효과"로도 생성 가능
        Person person2 = new Person.Builder().build();
        System.out.println(person2);

        Person person3 = new Person.Builder()
                .name("김삼")
                .build();
        System.out.println(person3);

        // Lombok @Builder
        Person2 person4 = Person2.builder()
                .name("김사")
                .build();
        System.out.println(person4);

        Person2 person5 = Person2.builder().build();
        System.out.println(person5);

        Person2 person6 = Person2.builder()
                .age(26)
                .name("김육")
                .address("광주광역시 북구")
                .build();
        System.out.println(person6);
    }
}

8. 동작 원리

빌더의 체이닝 메서드는 “값 세팅 + 자기 자신 반환”이다.

  • Builder 객체 생성
  • name()/age()/address() 호출할 때마다 Builder 내부 필드에 값 저장
  • 마지막에 build() 호출 → Person 생성자에 Builder를 넘김
  • Person은 Builder가 가지고 있던 값을 복사해서 완성된 객체가 됨

9. 주의점 / 확장 포인트

1) 필수값 강제

Builder는 편하지만, 아무 값도 안 넣고 build()가 가능해져서 필수값 누락 위험이 있다. 실무에선 보통 다음 방식 중 하나로 제어한다.

  • 필수 필드는 Builder 생성자에서 받기 (예: new Builder(name, school))
  • @Builder + @NonNull 사용 (널 방지)
  • build()에서 validate() 수행 후 예외 던지기

2) 배열/리스트 필드

subjects, scores 같은 배열/리스트는 외부에서 참조가 공유되면 위험하다(얕은 복사). 필요하면 build() 시점에 복사해서 방어적 복사(defensive copy)하는 방식도 고려한다.

3) @AllArgsConstructor와의 관계

@AllArgsConstructor는 “매개변수 순서 의존” 문제가 그대로 남는다. 필드가 많아지는 DTO라면 @Builder가 더 안전한 선택이다.


10. 정리

Builder Pattern은 “생성자 매개변수 지옥”을 해결하기 위한 생성 패턴이다. 필드가 많아도 명시적이고 안전하게 객체를 만들 수 있고, 필드 추가/변경에도 호출부 수정이 최소화된다.

즉, 빌더 패턴은 단순 편의 문법이 아니라 유지보수성과 실수 방지를 위한 설계다.

profile
No Pain No Gain

0개의 댓글