Item 2 : 매개변수가 많다면 빌더 패턴을 고려하라!

Item 요약

정적 팩터리, 생성자 모두 객체를 생성하기 위한 메서드이다.

하지만 만약 같은 타입의 매개변수가 많다면? → 개발자는 해당 클래스의 문서를 찾아보고 각 매개변수의 의미를 파악해야 할것이다. 또한 서로 다른 자리에 넣는 실수를 범할 가능성이 높아진다.

이번 아이템은 이러한 문제점들을 해결하기 위해 빌터(Builder) 패턴을 사용하도록 한다.

점층적 생성자 패턴

필요한 매개변수를 받는 생성자를 끊임없이 만들어가는 방법

😅적기 힘들어서 변수 개수를 줄였어요! 하하

public class Item {
    private final String name;
    private final String company;
    private final int price;

    public NutritionFacts(String name, int price) {
        this(name, "", price);
    }

    public NutritionFacts(String name, String company, int price) {
        this(name, company, price);
    }

    public NutritionFacts(String name, String company, int price) {
        this.name = name;
        this.company = company;
        this.price = price;
    }
}

이 클래스의 인스턴스를 만드려면 원하는 매개변수를 포함한 생성자 중 가장 짧은 것을 호출하면 된다.

1장에서 살펴봤듯이 생성자에는 이름이 없다. 매개변수가 늘어날수록 개발자는 어디에 어느 변수를 넣어야할지 헷갈릴 것이과 코드를 작성하거나 읽는 것이 어려워질 것이다. 또한 개발자가 매개변수의 순서를 바꿔 넣어도 컴파일러는 알아차리지 못하고, 결국 런타임 에러를 일으킬 것이다.

보통 이런 생성자는 사용자가 설정하길 원치 않는 매개변수까지 포함하기 쉬운데, 어쩔 수 없이 그런 매개변수에도 값을 지정해줘야 한다.  그리고 겉으로만 보기에는 그리 나빠보이지 않을 수 있지만, 매개변수의 개수가 더 늘어나게 된다면 생성자 코드가 걷잡을 수 없게 될 것이다.  또한 매개변수의 입력을 받을 때 매개변수가 몇 개인지 주의해서 세어보아야 하고, 같은 데이터 타입의 매개변수를 실수로 바꿔서 입력하여도 컴파일러는 알아채지 못하고, 엉뚱한 동작을 하게 된다.

🤫아래의 예제부터는 서적의 예제를 참고하였습니다!@

자바빈즈(Java Beans) 패턴

매개변수가 없는 생성자로 객체를 만든 후, setter를 사용하여 매개변수의 값을 설정하는 방식

public class NutritionFacts {
    private int servingSize = -1;
    private int servings = -1;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public NutritionFacts() {}

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public void setFat(int fat) {
        this.fat = fat;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }

    public void setCarbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
    }
}

패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.  일관성이 깨진 객체가 만들어지면, 버그를 심은 코드와 그 버그 때문에 런타임에 문제를 겪는 코드가 물리적으로 멀리 떨어져 있기 때문에 디버깅도 만만치 않다.  이러한 문제 때문에 자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없으며 스레드 안전성을 얻으려면 프로그래머가 추가적으로 작업을 해줘야 한다.

#@ 빌더 패턴(Builder pattern)

빌더 패턴으로 인스턴스를 생성하는 과정!
클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출하여 빌더 객체를 얻는다. -> 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다. -> build 메서드를 호출해 필요한 객체를 얻는다.

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    static class Builder {
//필수 매개변수private final int servingSize;
        private final int servings;

//선택 매개변수 - 기본값으로 초기화private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

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

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

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

        public Builder carbohydrate(int val) {
            {carbohydrate = val; return this;}
        }

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

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

빌더 패턴의 특징과 장점

  • 빌더의 setter 메서드들은 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다. 
// 빌더 패턴 호출 예시
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                .calories(100)
                                .sodium(35)
                                .carbohydrate(27)
                                .build();

이 방식을 플루언트 API 혹은 메서드 연쇄 라고 한다.

또한 빌더 객체가 제공하는 일종의 세터 메서드 덕분에 코드를 읽고 사용하기 쉬워졌다!

  • 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋다.

각 계층의 클래스에 관련 빌더를 멤버로 정의하여 추상 클래스는 추상 빌더를, 구체 클래스는 구체 빌더를 갖게 한다.

빌더를 ‘계층적 빌더’로 설계해도 개발자는 그냥 빌더패턴과 동일하게 사용할 수 있다.

  • 가변인수(varargs) 매개변수를 여러 개 사용할 수 있다.

  • 빌더 패턴은 상당히 유연하다.

    빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수 있다. 객체가마 부여되는 일련번호와 같은 특정 필드는 빌더가 알아서 채우도록 할 수도 있다.

빌더 패턴의 단점

  • 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있다.
  • 매개변수가 4개 이상은 되어야 값어치를 한다.

빌더 패턴은 생성자나 정적 팩터리 메서드보다 장점이 많다. 매개변수가 늘어날 가능성이 있거나!, 다수의 필드가 같은 타입이면 빌더 패턴을 고려해보자!

좋은 웹페이지 즐겨찾기