롬복을 사용하면서 @Builder 어노테이션을 접하게 되었습니다.
빌더 패턴이 정확히 어떤 역할을 하는지 정리하고자 합니다.
public class Student {
// 필수 인자
private String name;
private int age;
// 선택 인자
private String grade;
// 생성자
public Student(String name, int age, String grade) {
this.name = name;
this.age = age;
this.grade = grade
}
}
먼저 일반적으로 볼 수 있는 생성자 패턴 방식입니다.
Student 클래스에 필수 인자는 "name" "age" 이고 선택적 인자는 "grade"로 가정하겠습니다.
위 클래스를 인스턴스로 생성하려면 아래 코드와 같습니다.
Student student = new Student("poll", 17, "A+")
혹은 grade 변수가 선택적 인자이기 때문에 null 대입도 가능합니다.
Student student = new ("tom", "15", null);
위 상황에서 인자가 더 늘어난다고 생각해볼까요?
Student student = new Student("kim", 15, null, "highschool", null, null, ... )
중간에 null 값을 대입한 인자들이 "선택적인" 변수로 추측이 되지만, 무척 가독성이 떨어지고
호출 코드 만으로 어떤 변수에 들어가는지 알기 어렵습니다.
자바 빈 패턴 (Java Bean Pattern)
위 상황에서 변수에 맞게 생성자를 더 만드는 것은 무척 비효율적입니다.
그래서 Setter 메소드를 사용해서 코드 가독성을 높히기 시작했습니다.
public class Student {
private String name;
private int age;
private String grade;
public Student(String name, int age, String grade){
this.name = name;
this.age = age;
this.grade = grade;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
Student student = new Student();
student.SetName("poll");
student.SetAge(16);
student.SetGrade("B+");
생성자를 사용한 방식보다 코드를 읽기 편해졌습니다.
하지만 자바 빈 패턴에는 큰 단점이 있습니다.
1. 객체 일관성이 깨진다.
객체를 한번 생성하고 끝나는 것이 아닌, Setter 메소드를 통해 계속
인스턴스를 변경하고 있습니다.
2. 변경 불가(immutable) 객체를 만들 수 없다.
private 로 멤버 변수 접근을 막아도 Setter 메소드로 변수에 접근할 수 있기
때문에 쓰레드 작업에 단점이 될 수 있습니다.
빌더 패턴 (Builder Pattern)
코드 가독성과 immutable 객체 생성의 장점을 모두 가진 것이 바로 빌더 패턴입니다.
public class StudentBuilder {
private String name;
private int age;
private String grade;
public StudentBuilder() {}
public StudentBuilder(String name, int age, String grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
public static class Builder {
private String name;
private int age;
private String grade;
public Builder() {}
public Builder SetName(String name) {
this.name = name;
return this;
}
public Builder SetAge(int age) {
this.age = age;
return this;
}
public Builder SetGrade(String grade) {
this.grade = grade;
return this;
}
public StudentBuilder build() {
return new StudentBuilder(name, age, grade);
}
}
}
먼저 중첩 클래스(nested classes) 를 선언하게 됩니다.
내부 클래스 Builder 를 static 으로 하는 이유는 StudentBuilder 의 멤버 변수 접근을 막기 위해서입니다.
참고 : https://softwareengineering.stackexchange.com/questions/225207
StudentBuilder studentBuilder = new StudentBuilder.Builder()
.SetName("poll")
.SetAge(16)
.SetGrade("A+")
.build();
위 처럼 메소드 체이닝 방식으로 객체를 생성하게 됩니다.
메소드 명을 통해 어떤 변수에 값을 대입하는지 알기 쉽고, 객체를 한번에 생성하므로
일관성을 지킬 수 있습니다.
단점으로는
1. 무분별한 빌더 패턴 사용은 코드 복잡성을 증가 시킨다.
여러개의 변수를 설정해야 하는 경우, 필수와 선택 요소가 많은 경우에는
무척 효율적이겠지만, 적은 경우에는 생성자패턴으로 충분하다고 생각합니다.
2. 변경 가능한 객체 생성에 적합하지 않다.
빌더 패턴의 장점으로는 코드 가독성, 변경 불가능한 객체 생성에
있으므로, 그 외에 사용은 다른 패턴이 적합합니다.
출처 : https://stackoverflow.com/questions/2829106/disadvantages-of-builder-design-pattern
https://johngrib.github.io/wiki/builder-pattern