객체를 생성할 때 생성자의 파라미터가 점점 늘어나기 시작하면, 코드는 빠르게 읽기 어려워진다. 특히 Java에서는 필드 수가 많아질수록 생성자 오버로딩이 늘어나거나, 어떤 값이 필수이고 어떤 값이 선택인지 한눈에 파악하기 어려워지는 문제가 발생한다.
이러한 문제를 해결하기 위해 등장한 생성 패턴 중 하나가 바로 빌더(Builder) 패턴이다. 빌더 패턴은 객체 생성 과정을 단계별로 분리함으로써 가독성을 높이고, 필수 값과 선택 값을 명확히 구분할 수 있도록 도와준다.
이번 글에서는 빌더 패턴이 등장하게 된 배경부터, Java에서 빌더 패턴을 어떻게 구현하고 활용할 수 있는지, 그리고 실제로 사용할 때 주의해야 할 포인트까지 정리해보고자 한다.
객체를 생성할 때 선택적인 필드가 늘어나면, 가장 먼저 떠올리게 되는 방법은 생성자 오버로딩이다. 필수 값만 받는 생성자부터, 선택 값을 하나씩 추가한 생성자를 계속 만들어가는 방식인데, 이를 **점층적 생성자 패턴(Telescoping Constructor Pattern)**이라고 한다.
public class Student {
private final int id;
private final String name;
private final String grade;
private final String phoneNumber;
public Student(int id, String name) {
this(id, name, "freshman", "010-0000-0000");
}
public Student(int id, String name, String grade) {
this(id, name, grade, "010-0000-0000");
}
public Student(int id, String name, String grade, String phoneNumber) {
this.id = id;
this.name = name;
this.grade = grade;
this.phoneNumber = phoneNumber;
}
}
위와같이 코딩하였을 때 문제점은 선택 필드가 늘어날수록 생성자의 개수는 빠르게 증가하고 호출부의 가독성이 매우 떨어진다. 또한 어떤 필드가 반드시 필요한지, 선택적인지 명확하게 드러나지 않는다.
// 코드만 보고 어떤 의미진지 파악하기 어렵다. 파라미터 순서를 알고 있어야만 이해가능하다.
// 심지어 타입이 모두 String인 경우 순서를 잘못넣어도 컴파일 에러가 발생하지 않는다.
Student student = new Student(
2016120091,
"임꺽정",
"Senior",
"010-5555-5555"
);
앞서 살펴본 점층적 생성자 패턴의 문제를 해결하기 위해, 빌더(Builder) 패턴을 적용하면 가독성, 안전성, 유지보수성을 모두 개선할 수 있다.
public class Student {
private final int id;
private final String name;
private final String grade;
private final String phoneNumber;
private Student(StudentBuilder builder) {
this.id = builder.id;
this.name = builder.name;
this.grade = builder.grade;
this.phoneNumber = builder.phoneNumber;
}
@Override
public String toString() {
return "Student { " +
"id=" + id +
", name='" + name + '\\'' +
", grade='" + grade + '\\'' +
", phoneNumber='" + phoneNumber + '\\'' +
" }";
}
// Builder 클래스 정의
public static class StudentBuilder {
private final int id; // 필수
private final String name; // 필수
private String grade = "freshman"; // 선택
private String phoneNumber = "010-0000-0000"; // 선택
public StudentBuilder(int id, String name) {
this.id = id;
this.name = name;
}
public StudentBuilder grade(String grade) {
this.grade = grade;
return this;
}
public StudentBuilder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public Student build() {
return new Student(this);
}
}
}
public class Main {
public static void main(String[] args) {
// 필수 값만 지정
Student s1 = new Student.StudentBuilder(2016120091, "임꺽정")
.build();
// 선택 값까지 지정
Student s2 = new Student.StudentBuilder(2016120092, "홍길동")
.grade("Senior")
.phoneNumber("010-5555-5555")
.build();
System.out.println(s1);
System.out.println(s2);
}
}