노션이 코드보기 편할거에요
public class User {
private final int age; // 필수
private final int phoneNumber; // 필수
private final int weight; // 선택
private final int tall; // 선택
private final int birthday; // 선택
}
점층적 생성자 패턴으로 생성을 했었다.
private User(int age, int phoneNumber) {
this.age = age;
this.phoneNumber = phoneNumber;
}
private User(int age, int phoneNumber, int weight) {
this(age, phoneNumber);
this.weight = weight;
}
private User(int age, int phoneNumber, int weight, int tall) {
this(age, phoneNumber);
this.weight = weight;
this.tall = tall;
}
private User(int age, int phoneNumber, int weight, int tall, int birthday) {
this(age, phoneNumber);
this.weight = weight;
this.tall = tall;
this.birthday = birthday;
}
User user = new User(20, 99998888, 70, 180, 1225);
매개변수가 늘어날수록 **코드를 작성하거나 읽기 어렵다.
- 값이 무엇인지 헷갈린다.
- 매개 변수 몇 개인지도 주의해서 봐야한다.
- 순서가 바뀌게 된다면 컴파일러는 알아채지 못 한다.
public class User {
private int age = 1;
private int phoneNumber = 11111111;
private int weight;
private int tall;
private int birthday;
public void setAge(final int age) { this.age = age; }
public void setPhoneNumber(final int phoneNumber) { this.phoneNumber = phoneNumber; }
public void setWeight(final int weight) { this.weight = weight; }
public void setTall(final int tall) { this.tall = tall; }
public void setBirthday(final int birthday) { this.birthday = birthday; }
}
User user = new User();
user.setAge(20);
user.setPhoneNumber(99999999);
user.setWeight(70);
user.setTall(180);
user.setBirthday(1225);
점층적 생성자 패턴의 단점이 보완되었다. 더 읽기 쉬운 코드가 되었다.
하지만 심각한 단점을 가지고 있다.
- 객체 하나를 만들려면 메서드를 여러개 호출해야 한다.
- 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태가 된다.
- 일관성이 깨진 객체를 만들면 버그를 심은 코드와 버그 때문에 디버깅이 쉽지 않다.
→ 자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없으며 스레드 안전성을 얻으려면 추가적인 작업이 필요하다.
단점을 보완하고자 객체를 수동으로 얼리고(freezing) 얼리기 전에는 사용할 수 없도록 만든다.
- JS진영에는 freeze라는 메서드가 있지만 자바에서는 어떻게 만드는지 소개되지 않고 만들기 어려워 잘 사용하지 않는다고 한다.
점층적 생성자 패턴의 안전성 + 자바빈즈 패턴의 가독성 ⇒ 빌더 패턴
public class User {
private final int age;
private final int phoneNumber;
private int weight;
private int tall;
private int birthday;
public User(Builder builder) {
this.age = builder.age;
this.phoneNumber = builder.phoneNumber;
this.weight = builder.weight;
this.tall = builder.tall;
this.birthday = builder.birthday;
}
public static class Builder {
private final int age;
private final int phoneNumber;
private int weight;
private int tall;
private int birthday;
public Builder(int age, int phoneNumber) {
this.age = age;
this.phoneNumber = phoneNumber;
}
public Builder weight(int weight) {
// validation 가능
this.weight = weight;
return this;
}
public Builder tall(int tall) {
this.tall = tall;
return this;
}
public Builder birthday(int birthday) {
this.birthday = birthday;
return this;
}
public User build() {
return new User(this);
}
}
}
User user = new User.Builder(20, 99998888)
.weight(70)
.tall(180)
.birthday(1225)
.build();
User클래스는 불변하게 되었고, 모든 매개변수의 기본값들을 한곳에 모아뒀다.
빌더의 메서드는 자신을 반환하기 때문에 연쇄적으로 호출이 가능하다.
이런 방식을 플루언트API 혹은 메서드 연쇄라 한다.
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다. 각 계층의 클래스에 관련 빌더를 멤버로 정의하고 추상 클래스는 추상 빌더를 구체 클래스는 구체 빌더를 갖게 한다.
class SpicyRiceCake { // 떡볶이를 만들어보자
public enum Topping {HAM,EGG,BACON,CHEESE} // 토핑
final Set<Topping> toppings;
SpicyRiceCake(Builder<?> builder) {
toppings = builder.toppings;
}
abstract static class Builder<T extends Builder<T>> { // 추상 빌더
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(topping);
return self();
}
abstract SpicyRiceCake build();
protected abstract T self();
}
}
class DongDaeMoon extends SpicyRiceCake {
public enum Spicy {BASIC,NORMAL,GOD}
private final Spicy spicy;
DongDaeMoon(final Builder builder) {
super(builder);
spicy = builder.spicy;
}
public static class Builder extends SpicyRiceCake.Builder<Builder> {
private Spicy spicy;
public Builder(Spicy spicy) {
this.spicy = spicy;
}
@Override
SpicyRiceCake build() {
return new DongDaeMoon(this);
}
@Override
protected Builder self() { // 공변 반환 타이핑
return this;
}
}
}
SpicyRiceCake spicyRiceCake = new DongDaeMoon.Builder(BASIC)
.addTopping(BACON)
.addTopping(EGG)
.addTopping(CHEESE)
.build();
빌더 패턴은 상당히 유연하다. 빌더 하나로 여러 객체를 순회하면서 만들 수 있고 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있다.
장점만 있는 것은 아니다. 객체를 만들려면 빌더부터 만들어야 한다. 성능에 민감한 상황에서는 문제가 될 수 있다. 매개변수가 4개는 넘어야 값어치를 한다.