본문 바로가기

책, 강의 정리/이펙티브자바

아이템 [2] - 생성자 매개변수가 많다면 빌더를 고려하라

 

여러 생성자 패턴

심층적 생성자 패턴: 다양한 매개변수 조합을 가진 생성자를 생성해서 쓰는 패턴. 가독성이 좋지 않음. 모든 조합을 작성하지 않는 한 불필요한 값을 넣어야 할 수도 있음.

자바빈즈 패턴: 매개변수 없는 생성자 + 세터 조합. 하나의 객체를 생성하기 위해 세터 메서드를 여러번 호출해야하고, 그때까지 일관성이 유지되지 않음. freez 메서드를 사용할 수 있지만 컴파일러가 보증하지 못해 런타임 에러에 취약함.

빌더패턴

필수 매개변수만 호출. build 메서드를 호출해 최종 객체를 얻음. 일반적으로 정적 멤버 클래스로 만든다. 심층적 생성자 패턴은 유효성 검사가 여기저기 나누어져 있지만 빌더패턴의 경우 build 매서드나 메서드에서 호출하는 생성자에서 검사하면 된다. IllegalArgumentException 을 던지자.

package item2;

public class Foo {
    private final String name;
    private int age;
    private final String phone;
    private final String zip;

    public static class Builder{
        private final String name; //필수 매개변수

        private int age = 0;
        private String phone ="000-0000-0000";
        private String zip = "08500";

        public Builder(String name) {
            this.name = name;
        }

        public Builder age(int age){
            this.age = age;
            return this;
        }

        public Builder phone(String phone){
            this.phone = phone;
            return this;
        }

        public Builder zip(String zip){
            this.zip = zip;
            return this;
        }

        public Foo build(){
            return new Foo(this);
        }
    }

    public Foo(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.phone = builder.phone;
        this.zip = builder.zip;
    }

    @Override
    public String toString() {
        return "Foo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                ", zip='" + zip + '\'' +
                '}';
    }
}
package item2;

public class Main {

    public static void main(String[] args) {
        Foo foo = new Foo.Builder("foo")
                .age(17)
                .phone("010-2854-2518")
                .build();

        System.out.println(foo.toString());

    }
}

계층적으로 설계된 클래스와 함꼐 쓰기도 좋다.

package item2.pizza_ex;


import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

public abstract class Pizza {
    public enum TOPPING{ CHEEZE, OLIVE, SHRIMP}
    protected Set<TOPPING> toppings;

    public static abstract class Builder<T extends Builder>{
        EnumSet<TOPPING> toppings = EnumSet.noneOf(TOPPING.class);
        public T addTopping(TOPPING topping){
            toppings.add(topping);
            return self();
        }
        abstract Pizza build();
        protected abstract T self();
    }

    Pizza(Builder<?> builder){
        toppings = builder.toppings.clone();
    }

}
package item2.pizza_ex;

public class DominoPizza extends Pizza{
    public enum Size {SMALL, MEDIUM, LARGE};
    private final Size size;

    public static class Builder extends Pizza.Builder<Builder>{

        private Size size = Size.MEDIUM;

        public Builder(Size size) {
            this.size = size;
        }

        @Override
        public DominoPizza build() {
            return new DominoPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    public DominoPizza(Builder builder) {
        super(builder);
        this.size = builder.size;
    }

    @Override
    public String toString() {
        return "DominoPizza{" +
                "size=" + size +
                ", toppings=" + toppings +
                '}';
    }
}

빌더 생성 비용이 있기 때문에 매개변수가 4개 이상인 경우만. 하지만 API 는 시간이 지날수록 매개변수가 많아지는 경향이 있기 때문에 잘 고려한다.