[아이템2] 생성자에 매개변수가 많다면 빌더를 고려하라
점층적 생성자 패턴
- 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
public class Gradation {
private String name;
private int number;
private long money;
private double temperature;
public Gradation(String name, int number, long money, double temperature) {
this.name = name;
this.number = number;
this.money = money;
this.temperature = temperature;
}
public Gradation(String name, int number, long money) {
this(name, number, money, 0);
}
public Gradation(String name, int number) {
this(name, number, 0, 0);
}
public Gradation(String name) {
this(name, 0, 0, 0);
}
public Gradation() {
this("", 0, 0, 0);
}
}
Java Beans 패턴
- 객체 하나를 생성하기 위해 메서드를 여러번 호출해야한다.
- 객체를 완전히 생성하기 전까지 일관성이 무너진 상태에 놓인다.
- 이는 클래스를 불변으로 만들 수 없음을 의미한다.
public class JavaBeans {
private String name;
private int number;
private long money;
private double temperature;
public void setName(String name) {
this.name = name;
}
public void setNumber(int number) {
this.number = number;
}
public void setMoney(long money) {
this.money = money;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
}
class Beans {
public JavaBeans create() {
JavaBeans javaBeans = new JavaBeans();
javaBeans.setName("name");
javaBeans.setNumber(0);
javaBeans.setMoney(0);
javaBeans.setTemperature(0);
return javaBeans;
}
}
기본적인 Builder 패턴
public class Basic {
private String name;
private double height;
private double weight;
private int age;
public static class Builder {
private final String name; // final 예약어를 붙이게 된다면 필수 매개변수가 된다.
private double height;
private double weight;
private int age;
public Builder(String name) {
this.name = name;
}
public Builder height(double height) {
this.height = height;
return this;
}
public Builder weight(double weight) {
this.weight = weight;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Basic build() {
return new Basic(this);
}
}
private Basic(Builder builder) {
this.name = builder.name;
this.height = builder.height;
this.weight = builder.weight;
this.age = builder.age;
}
}
계층적으로 설계된 클래스의 Builder 패턴
public abstract class Hamburger {
public enum Patty {PORK, BEEF, CHICKEN, FISH;}
private final Set<Patty> patties;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Patty> patties = EnumSet.noneOf(Patty.class);
public T addPatty(Patty patty) {
patties.add(patty);
return self();
}
abstract Hamburger build();
protected abstract T self(); // 자식 클래스에서 형변환 없이 메서드 연쇄를 지원한다.
}
Hamburger(Builder<?> builder) {
patties = builder.patties.clone();
}
}
class McDonaldsHamBurger extends Hamburger {
private final boolean sauceInside;
public static class Builder extends Hamburger.Builder {
private final boolean sauceInside;
private Builder(boolean sauceInside) {
this.sauceInside = sauceInside;
}
@Override
public McDonaldsHamBurger build() { // 구체 클래스를 반환하지 않고 구체의 하위 클래스를 반환함으로써 형변환에 신경쓰지 않아도 된다. 이를 공변 변환 타이핑이라 한다.
return new McDonaldsHamBurger(this);
}
@Override
protected Builder self() {
return this; // this 를 반환해야 메서드 연쇄가 가능해진다.
}
}
public static Builder builder(boolean sauceInside){
return new Builder(sauceInside);
}
McDonaldsHamBurger(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
class HamburgerStore {
public Hamburger make(){
Hamburger hamburger = McDonaldsHamBurger.builder(true)
.addPatty(Hamburger.Patty.BEEF)
.addPatty(Hamburger.Patty.CHICKEN)
.build();
return hamburger;
}
}
Builder 패턴은 위에 살펴본 기존의 패턴들 보다 유연하다는 장점을 가지고 있지만, Builder를 사용하기 위해 장황한 코드를 구현해야 한다는 단점 또한 존재한다.
따라서 Builder 패턴은 매개변수가 4개가 넘어가는 시점에서 그 장점을 발휘한다고 한다.
그러나 보통 클래스는 매개변수가 늘어가는 성향이 있으므로 처음부터 Builder를 구현해 놓고 사용해도 좋을 듯 하다.