class Book {
private final String author;
private final int name;
public Book(String author, int name) {
this.author = author;
this.name = name;
}
public Book(int name) {
this.author = null;
this.name = name;
}
}
Book book = new Book("이펙티브 자바");
혹은
class Book {
private final String author;
private final int name;
public Book(String author, int name) {
this.author = author;
this.name = name;
}
}
Book book = new Book(null, "이펙티브 자바");
Book book = new Book("조슈아 블로크", "이펙티브 자바", 3, "9788966262281", 36000");
예시의 경우 매개변수가 5개밖에 되지 않는데도 각 값들이 어떤 값을 나타내는지 이해하기 어렵다.
만약 동일한 타입을 사용하는 매개변수가 연달아 있다고 할 때 클라이언트에서 잘못해서 값을 넘기게 되면 찾기 어려운 버그로 이어질 수도 있다.
생성자와 정적 팩터리가 가지는 문제점을 해결하기 위해 자바 빈즈 패턴을 사용해 보자.
매개변수가 없는 기본 생성자를 생성하고 setter 메서드를 호출해 매개변수의 값을 설정하는 방식이다.
class Book {
private String author;
private int name;
public Book() {
}
public void setAuthor(String author) {
this.author = author;
}
public void setName(int name) {
this.name = name;
}
}
// 필요한 값만 setter 메서드를 호출해서 주입할 수 있다.
// 메서드 이름을 통해 어떤 값이 설정되는지 한눈에 볼 수 있다.
Book book = new Book();
book.setAuthor("조슈아 블로그");
book.setName("이펙티브 자바");
코드가 더 길어지긴 했지만 생성자와 정적 팩터리가 가지는 문제점들이 해결되는 것을 볼 수 있다.
하자만 자바 빈즈 패턴에는 치명적인 문제가 있다.
대안으로 점층적 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 겸비한 빌더 패턴을 사용해 보자.
public class User{
private final String name; // 필수
private final int age; // 필수
private final String socialLoginId; // 선택
private User(Builder builder){
this.name = builder.name;
this.age = builder.age;
this.socialLoginId = builder.socialLoginId;
}
private User(){}
public static class Builder{
private final String name;
private final int age;
private String socialLoginId = "-1"; // 선택 매개변수는 기본 값으로 세팅
public Builder(String name, int age){
this.name = name;
this.age = age;
}
public Builder setSocialLoginId(String socialLoginId){
this.socialLoginId = socialLoginId;
return this;
}
public User build(){
return new User(this);
}
}
}
클라이언트는 필요한 객체를 직접 만드는 대신 필수 매개 변수만으로 생성자를 호출해 빌더 객체를 얻고 빌더 객체가 제공하는 setter 메 서드를 통해 선택 매개변수들을 설정한다. 그리고 build 메 서드를 호출해 필요한 객체를 얻는다.
User user = User.Builder("user1", 15)
.setSocialLoginId("124345")
.build();
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
// return (T)this;
return self();
}
abstract Pizza build();
// 하위 클래스가 오버라이딩 하게끔 하여 형 변환하지 않고 사용할 수 있다.
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
Pizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
public Builder onlyNyPizza() {
System.out.println("Only NyPizza");
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
Pizza pizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.onlyNyPizza()
.build();
매개변수의 개수가 많거나 매개변수 중 다수가 필수가 아니거나 같은 타입이면 빌더 패턴을 고려하자.