객체 생성은 생성자를 통해 하거나 아이템 1에서 공부한 것처럼 정적 팩토리 메서드를 사용할 수 있다.
이제 매개변수가 많아질 때에 어떻게 객체 생성을 할 수 있을지 알아보자.

점층적 생성자 패턴(telescoping constructor pattern)

정적 팩터리와 생성자에는 똑같은 제약이 있다.
바로 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 점이다.
이럴 때, 프로그래머는 샘플 코드와 같이 점층적 생성자 패턴(telescoping constructor pattern)을 즐겨 쓴다.

public class NutritionFacts {
    ...

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }
}

하지만, 점층적 생성자 패턴을 사용할 경우, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
각 값의 의미가 헷갈리고, 순서를 바꿔 전달함으로써 런타임에 엉뚱한 동작을 하게 된다(아이템51).

자바빈즈 패턴(JavaBeans pattern)

이의 대안으로 매개변수가 없는 생성자로 객체를 만든 후, 세터(setter) 메서드를 호출해 원하는 매개변수의 값을 설정하는 자바빈즈 패턴(JavaBeans pattern)이 나왔다.
샘플 코드

NutritionFacts_JavaBeans cocaCola = new NutritionFacts_JavaBeans();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohdrate(27);

코드가 길어지기는 했지만, 인스턴스를 만들기 쉽고, 그 결과 더 읽기 쉬운 코드가 되었다.
하지만, 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성(consistency)가 무너진 상태에 놓이게 된다.
따라서, 자바빈즈 패턴에서는 클래스를 불변(아이템17)으로 만들 수 없고 스레드 안정성을 얻으려면 프로그래머의 추가 작업이 필요하다.

빌더 패턴(Builder Pattern)

점층적 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴(Builder pattern)을 알아보자.
샘플 코드

  1. 클라이언트는 필수 매개만으로 객체를 직접 만든다.
  2. 빌더 객체가 제공하는 일종의 세터 메서드로 원하는 선택 매개변수들을 설정한다.
  3. 매개변수가 없는 build 메서드를 호출해 필요한 객체를 얻는다.
NutritionFacts_Builder cocaCola = new NutritionFacts_Builder.Builder(240, 8)
        .calories(100)
        .sodium(35)
        .carbohydrate(27)
        .build();

클라이언트는 읽고 쓰기 쉬운 코드를 얻게 된다.
빌더 패턴은 명명된 선택적 매개변수(named optional parameters)를 흉내 낸 것이다.

잘못된 매개변수를 최대한 일찍 발견하려면 build 메서드에서 불변(invariant) 검사를 하고,
잘못된 경우엔 IllegalArgumentException으로 처리하면 된다.

또한, 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋다.
샘플 코드

 

추상 클래스는 추상 빌더를, 구체 클래스는 구체 빌더를 갖게 한다.
하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌,
그 하위 타입을 반환하는 기능을 공변 반환 타이핑(convariant return typing)이라고 한다.

샘플 코드를 참고하여 작성하면 아래와 같이 빌더 패턴을 적용할 수 있다.

public class PizzaStore {
    public static void main(String[] args) {
        NyPizza nyPizza = new NyPizza.Builder(NyPizza.Size.SMALL)
                .addTopping(Pizza.Topping.SAUSAGE)
                .addTopping(Pizza.Topping.ONION)
                .build();
        Calzone calzone = new Calzone.Builder()
                .addTopping(Pizza.Topping.HAM)
                .sauceInside()
                .build();
    }
}

 

빌더 패턴은 상당히 유연하다.
빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있다.
빌더 패턴은 아주 유용하지만, 생성 비용이 크지는 않지만, 성능에 민감한 상황에서는 문제가 될 수 있다.
매개변수가 4개 이상은 되어야 값어치를 하니 참고하자.

빌더 패턴 책으로 읽을 때엔 그냥 술술 읽혔는데, 직접 샘플 코드를 짜보니 Generic으로 구현하는 부분이 헷갈렸다.
웬만하면 다들 실습 코드를 짜보자!

반응형
복사했습니다!