빌더 패턴이 생성자보다 좋은 이유
객체를 만들 때 빌더 패턴이 왜 기존 생성자 방식보다 나은지 궁금하다.
1. 기존 생성자 방식, 뭐가 문제일까
일단 생성자 방식은 객체를 만들 때 필요한 값을 전부 한 번에 넘겨야 한다. 예를 들어, 유저 정보를 담는 클래스를 만들어보자:
// 기존 생성자 방식
public class User {
private String name;
private int age;
private String email;
private String phone;
private String address;
private boolean isActive;
public User(String name, int age, String email, String phone, String address, boolean isActive) {
this.name = name;
this.age = age;
this.email = email;
this.phone = phone;
this.address = address;
this.isActive = isActive;
}
}
// 이렇게 사용함
User user = new User("홍길동", 30, "hong@example.com", "010-1234-5678", "서울시 강남구", true);
간단할 땐 괜찮아 보인다. 하지만 좀 더 생각해보면 문제가 있는데:
- 매개변수가 너무 많음: 필드가 6개만 돼도 생성자가 엄청 길어지고 뭐가 뭔지 헷갈린다.
- 선택적 필드가 골치 아프다: 주소나 전화번호가 없어도 만들고 싶으면
null
이나 빈 문자열을 넣어야 한다. - 생성자 오버로드 지옥: 필드 조합마다 생성자를 따로 만들다 보면 코드가 엉망이 된다. 예를 들어:
// 오버로드 예시
public User(String name, int age) { ... }
public User(String name, int age, String email) { ... }
public User(String name, int age, String email, String phone) { ... }
// ... 계속 늘어남
이렇게 하면 코드가 길어지고 유지보수도 힘들다. 실수로 잘못된 생성자를 호출할 수도 있다.
2. 빌더 패턴이란?
빌더 패턴은 이런 문제를 깔끔하게 해결해주는데
객체를 단계별로 만들고, 필요한 값만 넣을 수 있게 해줌. 같은 User
클래스를 빌더로 바꿔보면:
// 빌더 패턴
public class User {
private final String name; // 필수
private final int age; // 필수
private final String email; // 선택
private final String phone; // 선택
private final String address; // 선택
private final boolean isActive; // 선택
private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
this.phone = builder.phone;
this.address = builder.address;
this.isActive = builder.isActive;
}
public static class Builder {
private final String name;
private final int age;
private String email = "";
private String phone = "";
private String address = "";
private boolean isActive = false;
public Builder(String name, int age) {
this.name = name;
this.age = age;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder isActive(boolean isActive) {
this.isActive = isActive;
return this;
}
public User build() {
return new User(this);
}
}
}
// 사용 예시
User user = new User.Builder("홍길동", 30)
.email("hong@example.com")
.phone("010-1234-5678")
.address("서울시 강남구")
.isActive(true)
.build();
// 최소한으로 만들고 싶을 때
User minimalUser = new User.Builder("김영희", 25).build();
이렇게 필수 필드(name, age)는 생성자로 강제하고, 나머지는 선택적으로 넣을 수 있다.
3. 빌더 패턴의 장점
다음과 같은 장점때문에 빌더패턴을 사용한다:
- 가독성 good: 메서드 이름으로 어떤 값이 들어가는지 바로 알 수 있음.
.email("hong@example.com")
보면 딱 email인 걸 알 수 있다. (생성자로 생성했던 경우 몇 변째 변수에 어떤 항목이 들어가는지 알고 있어야 함.) - 유연함: 선택적 필드를 자유롭게 넣거나 뺄 수 있음. 최소한의 정보만으로도 객체를 만들 수 있다. (위의 예시처럼)
- 불변성 보장:
final
키워드를 써서 객체가 만들어진 후에 값이 바뀌지 않게 할 수 있다. 실수로 데이터가 꼬일 일이 없음. - 생성자 하나로 끝: 오버로드 여러 개 만들 필요 없이 빌더 하나로 모든 경우를 커버함.
- 유효성 검사 쉬움:
build()
메서드에서 값이 제대로 들어갔는지 체크할 수 있음.
팁: 예를 들어, 이메일 형식이 잘못됐으면 build()
에서 예외를 던지게 만들면 더 안전해진다.
4. 실전에서 어떻게 쓰이는지?
실제 프로젝트에서 빌더 패턴은 복잡한 객체를 만들 때 빛을 발한다. 예를 들어, 설정 객체를 만들 때:
// 서버 설정 객체
ServerConfig config = new ServerConfig.Builder("myServer", 8080)
.timeout(5000)
.maxConnections(100)
.sslEnabled(true)
.build();
이런 식으로 설정값이 많아도 깔끔하게 정리할 수 있음.
라이브러리에서도 자주 보는데, OkHttpClient
나 Retrofit
같은 데서 빌더 패턴을 써서 설정을 유연하게 처리한다.
5. @Builder 등장 (Lombok 사용)
Lombok의 @Builder를 쓰면 위 코드를 엄청 간단하게 줄일 수 있다.
자바 프로젝트에 Lombok 의존성 추가하고 이렇게 쓰면 끝:
import lombok.Builder;
@Builder
public class User {
private String name;
private int age;
private String email;
}
User user = User.builder()
.name("홍길동")
.age(30)
.email("hong@example.com")
.build();
`@Builder` 하나로 빌더 클래스랑 메서드 체이닝 다 만들어준다.
(Lombok이 컴파일 때 필요한 코드를 자동 생성해준다.)
5. @Builder 에서 필수 필드 설정
`@Builder`도 기본값이나 필수 필드를 설정할 수 있다. 예를 들어:
import lombok.Builder;
import lombok.NonNull;
@Builder
public class User {
@NonNull
private String name; // 필수 필드
private int age;
@Builder.Default
private String email = "없음"; // 기본값 설정
}
User user = User.builder()
.name("홍길동") // 필수라 안 넣으면 컴파일 에러
.age(30)
.build(); // email은 "없음"으로 설정됨
`@NonNull`으로 필수 필드를 강제하고, `@Builder.Default`로 기본값 줄 수 있다. (매우 편리)
6. 뭐가 제일 좋을까?
- 필드가 2~3개면 그냥 생성자 쓰자.
- 복잡한 객체인데 Lombok 못 쓰면 수작업 빌더로 가고.
- Lombok 쓸 수 있으면 `@Builder`로 편하게 가자
'Design Pattern' 카테고리의 다른 글
프록시 패턴 정리 (0) | 2025.04.01 |
---|---|
전략 패턴(Strategy Pattern) 정리 - 템플릿 메서드 패턴과의 차이점 (0) | 2025.03.27 |
[디자인 패턴] 템플릿 메서드 패턴 (0) | 2025.03.24 |
댓글