객체를 생성할 때, 특히 많은 필드를 가진 클래스에서는 생성자에 너무 많은 파라미터가 필요해지는 문제가 생깁니다. 이런 경우 가독성이 떨어지고, 실수를 유발하기 쉬운 코드가 되며, 유지보수도 점점 어려워집니다. 이 문제를 해결하기 위한 대표적인 디자인 패턴이 바로 Builder 패턴입니다. 요즘 많이 볼 수 있는 서브웨이(Subway)와 비슷하다고 생각합니다. 메뉴선택(builder) -> 재료선택 (필드 값 입력) -> 결재(Build)를 절차대로 진행하면서 다양한 조합으로 '샌드위치(객체)'를 만들 수 있는 점이 똑같지 않나요?
빌더 패턴은 왜 등장했을까?
너무 많은 생성자 파라미터
아래의 User 클래스를 만들었습니다.
public class User {
private String name;
private int age;
private String phone;
private String address;
private String email;
public User(String name, int age, String phone, String address, String email) {
this.name = name;
this.age = age;
this.phone = phone;
this.address = address;
this.email = email;
}
}
그러나 생성자에 전달해야 할 인자가 많을 경우 여러 문제가 발생합니다. 실제로 파라미터가 많아지면 순서가 맞았나 확인하는 경우가 매우 많습니다.
- 코드의 가독성이 급격히 떨어짐
- 파라미터 순서를 헷갈려 실수할 가능성이 높아짐
- 생성자 오버로딩(overloading)이 기하급수적으로 늘어나며 유지보수 부담 증가
단계적이고 명시적인 객체 생성을 위한 고민
빌더 패턴은 객체를 단계적으로, 명확하게 생성할 수 있도록 도와줍니다. 또한 선택적 필드를 유연하게 설정하고, 불변 객체(Immutable Object)를 쉽게 만들 수 있는 장점이 있습니다. 이러한 필요와 문제를 해결하고자 Builder 패턴이 등장했습니다.
빌더 패턴의 구조
핵심 구성 요소
- Product: 생성할 복잡한 객체 (예: User)
- Builder: 객체 생성을 위한 메서드를 정의 (인터페이스 또는 추상 클래스)
- ConcreteBuilder: Builder 구현 클래스
- Director: 객체 생성의 순서를 정의 (굳이 없어도 된다.)
디렉터 객체를 사용하면 많이 사용하는 미리 선택된 조합으로 객체를 생성할 수 있습니다, 간편하게. 마치 썹픽처럼!
UML 다이어그램 설명
+---------------------+
| Director |
+---------------------+
| - builder: Builder |
| +construct() |
+---------------------+
|
v
+---------------------+
| Builder |<----------------+
+---------------------+ |
| +setPartA() | |
| +setPartB() | |
| +build() | |
+---------------------+ |
^ |
| |
+---------------------+ |
| ConcreteBuilder |-----------------+
+---------------------+
| - product: Product |
| +setPartA() |
| +setPartB() |
| +build(): Product |
+---------------------+
Java 빌더 패턴 예제
public class User {
private final String name;
private final int age;
private final String phone;
private final String address;
private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public static class Builder {
private String name;
private int age;
private String phone;
private String address;
public Builder(String name) {
this.name = name;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
실제 사용 예시
User user = new User.Builder("Tom")
.age(30)
.phone("010-1234-5678")
.address("Seoul")
.build();
필수 필드(name)는 Builder 생성자에서 받고, 선택 필드는 메서드를 통해 설정합니다. 어떤 필드를 설정하고 있는지 명확히하여 가독성을 향상할 수 있습니다.
빌더 패턴의 장단점
장점
- 유연성 및 명확성 강화
- 필드 순서와 관계 없이 설정 가능
- 어떤 속성을 설정하는지 명확히 표현 가능
- 불변성과 유지보수 용이성
- final 필드를 통한 불변 객체 생성
- 새로운 필드가 추가될 경우 Builder 클래스만 수정하면 됨
단점
- 코드의 증가
- 클래스 하나당 Builder를 작성해야 함
- 필드를 Product와 Builder에 중복 선언해야 함
- 런타임 오류 위험
- 필수 값 누락 시 컴파일 단계에서 확인 불가능
- build() 메서드에서 수동으로 검증 로직 필요
빌더 패턴을 언제 사용해야 할까?
선택적 인자가 많은 객체를 생성할 때
불변 객체를 만들고자 할 때
객체 생성 과정을 더 명확하고 유연하게 표현하고 싶을 때
Java에서는 Lombok과의 통합을 통해 보다 간편하기 빌더패턴을 사용할 수 있습니다.
@Builder
애노테이션을 사용하면 Builder 클래스를 자동 생성해줍니다. 따라서, 코드의 간결성과 생산성 향상할 수 있습니다.
테스트 및 유효성 검증 팁
유효성 검증을 위해 build()메서드 내부에서 Null 체크나 조건 등을 검증하는 로직이 필요하게 됩니다. 필수 값이 누락되는 경우를 방지하기 위해서 입니다. 또한 테스트에서는 테스트 코드 작성을 유연하게 구성할 수 있도록 해줘 테스트 코드 작성을 쉽게 만들어 줍니다.
빌더 패턴은 복잡한 객체를 명확하고 안전하게 생성할 수 있게 해주는 디자인 패턴입니다. 특히 필수 값과 선택 값을 구분하고, 불변 객체를 생성해야 하는 상황에서 매우 유용하게 쓰입니다. 코드는 다소 길어질 수 있지만, 그만큼 가독성과 유지보수성, 안정성 면에서 장점이 많습니다.
'개발자 공부' 카테고리의 다른 글
디자인 패턴 - 방문자(Visitor) 패턴, 하고 싶은 일은 방문기사님께 (0) | 2025.04.24 |
---|---|
디자인 패턴 - 추상 팩토리(Abstract Factory), 제품군을 생성하는 공장 (0) | 2025.04.22 |
디자인 패턴 - 중재자(Mediator), 통신의 중앙집중화 (1) | 2025.04.20 |
디자인 패턴 - 플라이웨이트(Flywight), 더 가볍게 (0) | 2025.04.15 |
디자인 패턴 - 옵저버(Observer), 효과적인 상태관리 (0) | 2025.04.13 |