

빌더 패턴 정리
DesignPattern
2025.10.22.
1. 빌더 패턴
- 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법
- 멤버 변수가 많고, 생성자가 많아질 경우 복잡하고 파악하기 힘든 생성과정이 생기는 것이므로, 이를 순차적이고 선언적인 객체 생성 프로세스로 바꾸는 방법
2. 빌더 패턴 구현 방법
- 빌더 패턴 구현의 요점은, 메소드 체이닝
- 메소드 체이닝으로 엔티티의 데이터를 세팅해주는 빌더라는 인터페이스를 만들어 준 후 계속 스스로를 반환하고, 마지막에 해당 객체를 반환토록 하는 것
구현 설명
- APP : 클라이언트의 역할을 하는 객체
- TourDirector : 빌더 패턴을 적용할 TourPlanBuilder를 목적에 맞게 미리 구현해둔 메소드의 집합을 가지는 객체
- TourPlanBuilder : 빌더 패턴 적용한 interface
- DefaultTourPalnBuilder : TourPlanBuilder의 구현체
TourPlanBuilder
public interface TourPlanBuilder {
//메서드 체이닝 사용 → 인터페이스에서 정의한 또 다른 기능을 사용하기 위해서
TourPlanBuilder title(String title);
TourPlanBuilder nightsAndDays(int nights, int days);
TourPlanBuilder startDate(LocalDate localDate);
TourPlanBuilder whereToStay(String whereToStay);
TourPlanBuilder addPlan(int day, String plan);
//getPlan 을 호출하기 전가지는 계속 체이닝 사용 (마지막 단계이기에 검증하기 좋은 위치)
TourPlan getPlan();
}
public class DefaultTourBuilder implements TourPlanBuilder{
private String title;
private int nights;
private int days;
private LocalDate startDate;
private String whereToStay;
private List<DetailPlan> plans;
@Override
public TourPlanBuilder nightsAndDays(int nights, int days) {
this.days = days;
this.nights = nights;
return this;
}
...
@Override
public TourPlanBuilder addPlan(int day, String plan) {
if (this.plans == null) {
this.plans = new ArrayList<>();
}
this.plans.add(new DetailPlan(day, plan));
return this;
}
@Override
public TourPlan getPlan() {
return new TourPlan(title, nights, days, startDate, whereToStay, plans);
}
}
Client
- 빌더 패턴을 적용해 객체 생성 가능
- 빌더 패턴을 적용하면 선언적이고, 순차적인 구조로 객체 생성의 프로세스를 작성 가능
- 또한 빌더패턴을 이용해서 초기 Builder 를 선언하는 부분을 이용한다면 파라미터를 이용해 강제적으로 데이터 세팅이 가능하도록 설계 가능
public class App {
public static void main(String[] args) {
TourPlanBuilder builder = new DefaultTourBuilder();
TourPlan plan = builder.title("칸쿤여행")
.nightsAndDays(2, 3)
.startDate(LocalDate.of(2020, 10, 2))
.whereToStay("hotel")
.addPlan(0, "저녁 식사")
.addPlan(1, "아침 식사")
.getPlan();
TourPlan longBeachTrip = builder.title("롱비치")
.startDate(LocalDate.now())
.getPlan();
TourPlan tourPlan = DefaultTourBuilder.Builder("title").getPlan();
// Director를 사용해 프로세스가 구성되어 사용하는 코드로 바뀌게 된다.
TourDirector director = new TourDirector(builder);
TourPlan cancun = director.cancunTrip();
TourPlan longBeach = director.longBeachTrip();
}
}
TourDirector
public class TourDirector {
private TourPlanBuilder tourPlanBuilder;
public TourDirector(TourPlanBuilder tourPlanBuilder) {
this.tourPlanBuilder = tourPlanBuilder;
}
public TourPlan cancunTrip() {
return tourPlanBuilder
.title("칸쿤여행")
.nightsAndDays(2, 3)
.addPlan(0, "체크인하고 짐 풀기")
.getPlan();
}
public TourPlan longBeachTrip() {
return tourPlanBuilder
.title("긴해변여행")
.nightsAndDays(2, 3)
.addPlan(0, "체크인하고 짐 풀기")
.getPlan();
}
}
빌더 패턴 네이밍 형식
- 대표적으로 3가지 존재
- 멤버이름()
- set멤버이름()
- with멤버이름()
3. 장점과 단점
장점
- 객체 생성 과정을 일관된 프로세스로 표현
- 디폴트 매개변수 생략을 간접적으로 지원
- 디폴트 매개변수가 설정된 필드를 설정하는 메서드를 호출하지 않는 방식
- 필수 멤버와 선택적 멤버를 분리 가능
- 객체 생성 단계를 지연할 수 있음
- 객체 생성을 단계별로 구성하거나 구성 단계를 지연하거나 재귀적으로 생성을 처리할 수 있다.
java// 1. 빌더 클래스 전용 리스트 생성
List<StudentBuilder> builders = new ArrayList<>();
// 2. 객체를 최종 생성 하지말고 초깃값만 세팅한 빌더만 생성
builders.add(
new StudentBuilder(2016120091)
.name("홍길동")
);
builders.add(
new StudentBuilder(2016120092)
.name("임꺽정")
.grade("senior")
);
builders.add(
new StudentBuilder(2016120093)
.name("박혁거세")
.grade("sophomore")
.phoneNumber("010-5555-5555")
);
// 3. 나중에 빌더 리스트를 순회하여 최종 객체 생성을 주도
for(StudentBuilder b : builders) {
Student student = b.build();
System.out.println(student);
}
- 초기화 검증을 멤버별로 분리
- 멤버에 대한 변경 가능성 최소화를 추구
- 클래스 맴버 초기화를 Setter를 통해 구성하는 것은 매우 좋지 않은 방법이다.
- 불변 객체와 연관이 있는데, 불변 객체는 오로지 읽기(get)메소드만을 제공하며 쓰기(set)은 제공하지 않는다. (자바의 final)
- 불변 객체의 장점
- 불변 객체는 Thread-Safe하여 동기화를 고려하지 않아도 된다.
- 만일 가변 객체를 통해 작업을 하는 도중 예외(Exception)이 발생하면 해당 객체가 불안정한 상태에 빠질 수 있어 또 다른 에러를 유발할 수 있는 위험성이 있기 때문이다.
- 불변 객체로 구성하면 다른 사람이 개발한 함수를 위험없이 이용을 보장할 수 있어 협업에도 유지보수에 유용하다.
- 불변 객체와 연관이 있는데, 불변 객체는 오로지 읽기(get)메소드만을 제공하며 쓰기(set)은 제공하지 않는다. (자바의 final)
즉, 빌더 패턴은 생성자 없이 어느 객체에 대해 ‘변경 가능성을 최소화’를 추구해 불변성을 갖게 해준다.
단점
- 코드 복잡성 증가
- 생성자 보다는 성능이 떨어진다.
- 지나친 빌더 남용은 금지
- 필드 개수가 적고, 변경 가능성이 없는 경우라면 차라리 생성자나 정적 팩토리 메소드를 이용하는 것이 좋을 수 있다.
4. Lombok @Builder가 생성하는 코드
- Lombok은 리플렉션을 이용해, 컴파일 시 해당 어노테이션이 등록된 위치에 맞춰 정의된 기능을 주입
- @Builder가 적용된 객체는 위에서 살펴보았던 빌더패턴이 그대로 객체 내부 코드에 삽입된 걸 확인해 볼 수 있다.
public class TourPlan {
private String title;
private int nights;
private int days;
private LocalDate startDate;
private String whereToStay;
private List<DetailPlan> plans;
public TourPlan() {
}
public TourPlan(String title, int nights, int days, LocalDate startDate, String whereToStay, List<DetailPlan> plans) {
this.title = title;
this.nights = nights;
this.days = days;
this.startDate = startDate;
this.whereToStay = whereToStay;
this.plans = plans;
}
public static TourPlanBuilder builder() {
return new TourPlanBuilder();
}
public static class TourPlanBuilder {
private String title;
private int nights;
private int days;
private LocalDate startDate;
private String whereToStay;
private List<DetailPlan> plans;
TourPlanBuilder() {
}
public TourPlanBuilder title(final String title) {
this.title = title;
return this;
}
public TourPlanBuilder nights(final int nights) {
this.nights = nights;
return this;
}
public TourPlanBuilder days(final int days) {
this.days = days;
return this;
}
public TourPlanBuilder startDate(final LocalDate startDate) {
this.startDate = startDate;
return this;
}
public TourPlanBuilder whereToStay(final String whereToStay) {
this.whereToStay = whereToStay;
return this;
}
public TourPlanBuilder plans(final List<DetailPlan> plans) {
this.plans = plans;
return this;
}
public TourPlan build() {
return new TourPlan(this.title, this.nights, this.days, this.startDate, this.whereToStay, this.plans);
}
public String toString() {
return "TourPlan.TourPlanBuilder(title=" + this.title + ", nights=" + this.nights + ", days=" + this.days + ", startDate=" + this.startDate + ", whereToStay=" + this.whereToStay + ", plans=" + this.plans + ")";
}
}
}
5. 실무에서 찾아보는 Builder 패턴
- StringBuilder
- 빌더에 해당하는 StringBuilder를 생성하고, 빌더가 제공하는 append 메서드로 파라미터를 구성하고, 최종적으로 toString을 호출해서 String 객체를 생성하는 일련의 과정이 빌더 패턴
- StreamBuilder
- Stream에 들어갈 요소를 add하고, 최종적으로 build를 호출해 stream 객체를 생성
- Uricomponentsbuilder