[Effective Java] 2 - item2 생성자에 매개변수가 많다면 빌더를 고려하라.
item2 생성자에 매개변수가 많다면 빌더를 고려하라.
정적 팩터리와 생성자는 선택적 매개변수가 많을수록 대응하기 힘들다.
점층적 생성자 패턴 (telescoping constructor pattern)
public class Coffee {
private int coffeeId;
private String coffeeName;
private int shots;
private String size;
...
public Coffee(int coffeeId, String coffeeName) {
this.coffeeId = coffeeId;
this.coffeeName = coffeeName;
}
public Coffee(int coffeeId, String coffeeName, int shots) {
this.coffeeId = coffeeId;
this.coffeeName = coffeeName;
this.shots = shots;
}
...
}
- 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 2개까지 받는 생성자, … 선택 매개변수를 전부 받는 생성자를 작성하는 방식
- 매개변수 개수가 많아지면 클라이언트 코드를 작성하기 힘들고 가독성 떨어짐
자바 빈즈 패턴(JavaBeans pattern)
Coffee coffee = new Coffee();
coffee.setName("Americano");
coffee.setShots(1);
coffee.setSize("Small");
...
- 매개변수 없는 생성자로 객체 생성 후,
setter()
메서드들을 호출해 원하는 매개변수의 값 설정 - 한 객체를 생성하기 위해 메서드 여러개 호출해야하고(비용이 높아짐), 이 객체가 완전히 생성되기 전까지는 일관성 유지되지 않음
- 클래스를 불변으로 만들 수 없으므로 스레드 안정성을 위한 추가 작업이 필요
빌더 패턴
public class Coffee {
private final int coffeeId;
private final String coffeeName;
private final int shots;
private final String size;
// Private 생성자로 외부에서 직접 객체를 생성하지 못하게 하고,
// 해당 객체의 Builder 객체로만 생성이 가능하게 한다.
private Coffee(Builder builder) {
this.coffeeId = builder.coffeeId;
this.coffeeName = builder.coffeeName;
this.shots = builder.shots;
this.size = builder.size;
}
public static class Builder { // 빌더 클래스를 따로 생성. 정적 클래스이다.
// 필수 매개변수
private final int coffeeId;
private final String coffeeName;
// 선택 매개변수 : 기본값으로 초기화할수 있다.
private int shots = 0;
private String size = "Small";
public Builder(int coffeeId, String coffeeName) {
// 필수 매개변수
this.coffeeId = coffeeId;
this.coffeeName = coffeeName;
}
public Builder shots(int shots) {
this.shots = shots;
return this;
}
public Builder size(String size) {
this.size = size;
return this;
}
// Coffee 객체를 생성하여 반환한다.
public Coffee build() {
return new Coffee(this);
}
}
}
- 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻을 수 있음
- 일종의 세터 메서드들을 통해 선택 매개변수들을 설정하고, 마지막에 build 메소드를 호출해 객체를 얻음
Coffee coffee = new Coffee.builder(1, "Espresso")
.shots(2)
.size("Large")
.build();
- 메서드 체이닝(method chaining)을 통해 가독성 있게 객체를 생성할 수 있다.
- 불변인 클래스를 생성한다.
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 적합하다.
public abstract class Member {
// 모든 서브 타입 객체에 공통적으로 필요한 타입
public enum Authority { READ, WRITE, DELETE }
final Set<Authority> authorities;
// 재귀적 타입 파라미터를 가진 제너릭 타입
abstract static class Builder<T extends Builder<T>> {
EnumSet<Authority> authorities = EnumSet.noneOf(Authority.class);
// 서브타입에서 권한을 정의할 수 있음
public T addAuthorities(Authority types) {
authorities.add(Objects.requireNonNull(types));
return self();
}
abstract Member build();
// 하위 클래스는 반드시 자기 자신을 반환하는 메서드를 오버라이드 해야 한다.
protected abstract T self();
}
Member(Builder<?> builder) {
authorities = builder.authorities.clone();
}
}
public class Guest extends Member {
private final String name;
public static class Builder extends Member.Builder<Builder> {
private final String name;
// 1. Guest 객체가 가져야할 인스턴스 변수
public Builder(String name) { this.name = Objects.requireNonNull(name); }
// 2. 마지막으로 호출되는 빌드 완성 메서드
// - 공변 반환 타이핑(covariant return typing)을 사용하면, 빌더를 사용하는 클라이언트는 캐스팅이 필요없다.
@Override Guest build() { return new Guest(this); }
// 3. 부모 타입에서 필요한 서브 타입의 참조
@Override protected Builder self() { return this; }
}
private Guest(Builder builder) {
super(builder);
name = builder.name;
}
}
// 상위 타입의 Members에 정의된 Authorities를 원하는 대로 추가할 수 있다.
Guest guest = new Guest.Builder("myguests")
.addAuthorities(Member.Authority.READ)
.addAuthorities(Member.Authority.WRITE).build();
- 가변 인수 매개변수를 여러개 사용할 수 있다.
- 공변 반환 타이핑(covariant return typing)
- 클라이언트가 형변환에 신경 쓰지 않고도 빌더를 사용할 수 있다.
- 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 하위 타입(Guest)을 반환한다.
- 코드 출처
단점
- 객체를 만들기 위해, 빌더부터 생성해야 하므로 성능에 민감한 상황에서는 문제가 될 수 있다.
- 매개변수가 4개 이상은 되어야 빌더 패턴의 가치가 있다.
Leave a comment