Lombok을 활용하면 빌더 패턴을 보일러플레이트 코드 없이 간단하게 구현할 수 있습니다.
그러다 보니 문득 이런 생각이 들었습니다.
"이 패턴은 왜 사용하는 걸까?"

파라미터를 유연하게 조절할 수 있다는 점은 알고 있었지만,
빌더 패턴의 본질이 단순히 편의성에만 있는 것은 아닐 것 같았습니다.
과연 이렇게 이해하는 것만으로 충분한 걸까요?
왜 탄생하게 되었을까?
빌더 패턴은 왜 탄생했을까요? 초창기 객체는 파라미터가 간단했다고 합니다.
필드 몇개 생성자 하나...
class A {
int a;
int b;
public A(int a, int b) {
this.a = a;
this.b = b;
}
}
하지만 필드는 증가하고 필수 값과 선택 값들이 혼재되기 시작했습니다. 어떨때는 생성 조건이 도메인마다 달라졌습니다.
객체 하나에 N개의 생성자가 생길 수도 있다는 뜻이 되죠.
class B {
int a;
int b;
int c;
int d;
int e;
int f;
pubic B(int a,int b,int c, int d,int e, int f) {
...
}
pubic B(int a,int b,int c, int d,int e) {
...
}
pubic B(int a,int b,int c, int d) {
...
}
pubic B(int a,int b,int c, int d) {
...
}
pubic B(int a,int b,int c) {
...
}
...
}
유연한 객체 생성을 지원하려면 이러한 방식 외에는 선택지가 없어 보입니다.
하지만 이 구조는 곧 한계에 부딪힙니다.
생성자가 늘어날수록 객체가 어떤 의도로 생성되었는지 코드만으로 파악하기 어려워지고, 생성자 시그니처가 도메인 규칙을 설명하지 못하게 됩니다. 결국 객체 생성은 가능하지만, 의미는 사라진 상태가 됩니다. 이 지점에서 문제는 단순한 코드 관리가 아니라, 객체 생성 자체를 어떻게 표현해야 하는가로 바뀌게 됩니다.
이러한 문제의식 속에서 탄생한 패턴이 바로 빌더 패턴입니다.
어떻게 빌더 패턴은 위 문제를 해결 했을까?
빌더 패턴을 이야기하기에 앞서, 개발자들이 이 문제를 어떻게 해결해 왔는지를 순차적으로 살펴보겠습니다.
1. 생성자 오버로딩을 통한 해결
이 해결 방법은 위에서 확인을 했었죠. 필드 수가 늘어날수록 이 접근은 빠르게 한계를 드러냅니다.
생성자 시그니처가 증가하면서, 각 생성자가 어떤 의도를 가지는지 파악하기 어려워지고
파라미터의 순서와 조합에 강하게 의존하게 됩니다.
new B(1, 2, 3, 4);
new B(1, 2, 3, 4, 5);
이 코드만 보고 두 객체의 차이를 명확히 설명하기는 어렵습니다.
생성자는 객체를 만들 수는 있지만, 왜 이런 상태로 만들어졌는지를 충분히 설명하지 못합니다.
결국 생성자 오버로딩 방식은 객체 생성의 안정성은 확보했지만, 생성 의도를 코드에 남기지 못하는 문제를 해결하지는 못했습니다.
2. Setter를 이용한 객체 조립
생성자 오버로딩 방식의 한계가 드러나자, 다음으로 선택된 방법은 setter를 이용해 객체를 조립하는 방식이었습니다.
이 접근은 생성자에 모든 경우의 수를 담지 않아도 된다는 점에서 훨씬 유연해 보였습니다.
객체를 먼저 생성한 뒤, 필요한 값만 선택적으로 설정할 수 있었기 때문입니다.
B b = new B();
b.setA(1);
b.setB(2);
b.setC(3);
이 방식은 특히 선택 값이 많은 경우에 효과적으로 보였습니다.
필요한 필드만 설정할 수 있고, 생성자 시그니처가 복잡해지지 않는다는 장점도 있었습니다.
하지만 이 유연함은 곧 구조적인 문제로 이어집니다.
setter 기반 방식에서는 객체가 완성되지 않은 상태로 존재할 수 있습니다.
어떤 필드가 설정되었고, 어떤 필드가 아직 설정되지 않았는지는 객체 외부에서는 쉽게 알 수 없습니다.
또한 객체가 언제 "완성되었다"고 판단해야 하는지도 모호해집니다.
생성 시점에는 검증을 할 수 없고, 모든 setter 호출이 끝난 이후를 개발자가 암묵적으로 가정해야 합니다.
이로 인해 다음과 같은 문제가 발생합니다.
- 객체가 중간 상태로 사용될 가능성
- 생성 규칙과 검증 로직의 위치가 불명확해짐
- 불변 객체 설계가 사실상 불가능
- 객체 생명주기가 코드에 드러나지 않음
Setter는 객체를 생성하는 수단이 아니라, 이미 생성된 객체의 상태를 변경하는 메서드입니다.
따라서 setter 방식에서는 객체가 완성되기 이전부터 메모리에 존재하게 됩니다.
생성자 방식과 setter 방식 모두, 객체를 생성할 수는 있었지만
객체의 완성 시점을 책임지는 구조는 아니었습니다.
이 문제를 해결하기 위해 등장한 것이 바로 빌더 패턴입니다.
3. 그래서 빌더는 어떻게 해결한건데..?
빌더 패턴은 객체를 바로 생성하지 않는 전략을 선택했습니다. 기존 방식에서 new와 동시에 객체를 생성되었습니다.
하지만 빌더 패턴에서는 객체를 바로 만들지 않고, 먼저 Builder를 통해 필요한 값들을 조립합니다.
Builder → 조립 중인 상태
build() → 생성이자 완성 선언
이 구조 덕분에 객체는 완성되기 전까지 외부로 노출되지 않았습니다. 그렇다면 어떻게 만들었을까요?
빌더 패턴은 다음처럼 사용이 되어집니다.
A a A.builder()
.a(1)
.b(2)
.c(3)
.d(4)
.build();
그렇다면 내부는 어떻게 만들까요? 빌더 패턴은 객체 내부에 static inner class Builder를 두어, 객체 생성에 필요한 정보와 규칙을 캡슐화하고, build()라는 단일 시점에서만 객체를 생성하도록 설계되어집니다.
class A {
int a;
int b;
int c;
private A(Builder builder) {
this.a = builder.a;
this.b = builder.b;
this.c = builder.c;
}
static class Builder() {
private int a;
private int b;
private int c;
public Builder a(int a) {
this.a = a;
return this;
}
public Builder b(int b) {
this.b = b;
return this;
}
public Builder c(int c) {
this.c = c;
return this;
}
public A build() {
return new A(this);
}
}
}
Builder를 반환하도록 설계함으로써, 객체 생성 과정은 Builder 단계와 객체 단계로 명확히 분리됩니다.
Builder는 생성 중인 상태를 표현하고, build()는 그 상태를 완성된 객체로 확정하는 유일한 경계가 됩니다.
특이한 점은 생성자를 private으로 두었다는 점입니다.
이는 객체를 의도적으로 Builder를 통해서만 생성하도록 제한하기 위한 선택입니다.
왜 불변 객체와 궁합이 좋은가
객체를 static으로 둔다는 말의 의미는, Builder가 객체의 인스턴스와 무관하게 클래스 레벨에 존재한다는 뜻입니다.
즉, 인스턴스가 없어도 Builder 생성이 가능해야 하며, Builder는 아직 생성되지 않은 객체를 알 필요가 없습니다.
이로 인해 Builder는 객체 생명주기 이전 단계에 위치하게 됩니다. 객체가 생성되기 전에 필요한 모든 값을 조립하고 검증한 뒤,
build() 시점에서만 단 한 번 객체를 생성합니다.
이 구조 덕분에 객체는 생성되는 순간부터 완성된 상태로만 존재하게 되며,
생성 이후 상태 변경이 불가능한 불변 객체 설계와 자연스럽게 맞물리게 됩니다.
그래서 빌더 패턴이 무엇인가?
빌더 패턴은 생성 패턴의 한 종류로, 객체 생성을 위한 파라미터 조합과 생성 과정을 단계적으로 분리해 관리합니다.
생성자의 파라미터가 많거나, 필수 값과 선택 값이 혼재된 경우 객체 생성의 의도를 명확하게 표현할 수 있다는 장점을 가집니다.
다만 빌더 패턴은 클래스 구조에 Builder를 추가하는 방식이기 때문에 단순한 객체에까지 무분별하게 적용할 경우
코드 복잡도를 불필요하게 증가시킬 수 있습니다. 따라서 빌더 패턴은 객체 생성 규칙이 복잡한 경우에 한해
의도적으로 선택하는 것이 바람직합니다.
'개발 > 디자인패턴' 카테고리의 다른 글
| 퍼사드 패턴 (0) | 2026.01.05 |
|---|---|
| 디자인패턴과 안티 패턴 (1) | 2025.12.25 |
| 팩토리 메소드 패턴 (0) | 2025.12.22 |
| 템플릿 메소드 패턴 (0) | 2022.03.19 |
| 퍼사드 패턴 (0) | 2021.12.05 |