본문 바로가기

자바

(이펙티브 자바) 아이템 02. 생성자에 매개변수가 많다면 빌더를 고려하라

 

선택적 매개변수가 많을 때 개발자들이 사용하는 방법 3

1. 점층적 생성자 패턴

생성자 형태를 모든 경우의 수로 만드는 방법

 

단점 :

  • 매개변수가 많아질수록 클라이어느 코드를 작성하거나 읽기 어렵다
  • 매개변수 세기도 어렵고...
  • 버그가 발생하면 찾기가 어렵다
public class Burger {
    private final boolean olive;
    private final boolean pickled;

    private final int patty;
    private final int egg;
    private final int cheese;
    private final int tomato;
    private final int bacon;
    
    public Burger (boolean olive, boolean pickled){
        this(olive, pickled, 1); 
        //이때 1은 코드를 효율적으로 짜기위해 없는 매개변수 부분을 1로 default 시키는 방법
    }
    public Burger (boolean olive, boolean pickled, int patty){
        this(olive, pickled, patty, 1); 
        //this는 생성자 자기 자신을 의미
    }
    
    ...
    
    public Burger (boolean olive, boolean pickled, int patty, int egg, int cheese, int tomato, int bacon){
        this.olive = olive;
        this.pickled = pickled;
        this.patty = patty;
        this.egg = egg;
        this.cheese = cheese;
        this.tomato = tomato;
        this.bacon = bacon;
        //이때 this는 위에서 선언한 필드를 의미
    }
}

 

2. 자바빈즈

Burger burger = new Burger();
burger.setOlive(true);
burger.setPickled(true);
burger.setPatty(1);
burger.setEgg(1);
burger.setCheese(1);
burger.setTomato(1);
burger.setBacon(1);

단점 :

  • 객체 하나를 완성하기 위해서는 메서드를 여러번 호출해야하고, 따라서 완성되기 전에 일관성이 무너지게 된다.
  • 클래스를 불변으로 만들 수 없다
  • 스레드 안정성을 위한 추가적 작업이 필요
  • '얼리고'를 사용하지만 실전에서 거의 사용 x
  • 컴파일러가 보증할 방법이 없어서 런타임 오류에 취약

3. 빌더 패턴

클래스는 불변이 되고, 모든 매개변수가 한 곳에 모여짐. 빌더의 세터 메서드들은 빌더 자신을 반환하여 연쇄적으로 호출가능(플루언트 API or 메서드 연쇄)

 

public class BurgerIngredients {

    private final boolean olive;
    private final boolean pickled;

    private final int patty;
    private final int egg;
    private final int cheese;
    private final int tomato;
    private final int bacon;

    public static class Builder {
        //필수로 올리브 피클 유무
        private final boolean olive;
        private final boolean pickled;

        // 선택 - 기본 한장 초기화(필수값만 final이고 선택은 final x)
        private int patty = 1;
        private int egg = 1;
        private int cheese = 1;
        private int tomato = 1;
        private int bacon = 1;

        public Builder(boolean olive, boolean pickled){
            this.olive = olive;
            this.pickled = pickled;
        }

        public Builder patty(int count)
        {   patty = count; return this; }
        public Builder egg(int count)
        {   egg = count; return this; }
        public Builder cheese(int count)
        {   cheese = count; return this; }
        public Builder tomato(int count)
        {   tomato = count; return this; }
        public Builder bacon(int count)
        {   bacon = count; return this; }

        public BurgerIngredients build(){
            return new BurgerIngredients(this);
        }
    }
    //Builder 안에 한꺼번에! 
    //BurgerIngredients의 필드들은 final 이기 때문에 클래스는 불변이 된다
    private BurgerIngredients(Builder builder){
        olive = builder.olive;
        pickled = builder.pickled;
        patty = builder.patty;
        egg = builder.egg;
        cheese = builder.cheese;
        tomato = builder.tomato;
        bacon = builder.bacon;
    }
}
//호출
//연쇄적으로 호출되는 모습
BurgerIngredients cheeseburger = new BurgerIngredients.Builder(true, true).cheese(2).build();

 

 

▶ 추가 : 계층적으로 설계된 클래스에 빌더 패턴 사용

import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;

public abstract class Hamburger {
    public enum Ingredient { PATTY, EGG, CHEESE, TOMATO, BACON}
    final Set<Ingredient> ingredients;

    abstract static class Builder <T extends Builder <T>> {
        EnumSet<Ingredient>  ingredients = EnumSet.noneOf(Ingredient.class); //EnumSet 정리
        public T addIngredient(Ingredient ingredient){
            ingredients.add(Objects.requireNonNull(ingredient)); //static method 정리
            return self();
        }
        abstract Hamburger build();

        protected abstract T self(); //셀프타입 관용구 - 추상메서드
    }
    Hamburger(Builder<?> builder){ //어떤 햄버거를 만들지 모르니 위에서부터 다 제네릭
        ingredients = builder.ingredients.clone(); //NutritionFacts 클래스 불변 참조
    }
}
※ EnumSet

EnumSet 은 Enum을 담는 변수로 List<String>은 String을 담는 변수라면 EnumSet은 Enum을 담는 그릇 EnumSet.noneOf 는 EnumSet을 빈그릇으로 만드는 것.
대신 이 그릇엔 Ingredient 라는 Enum의 값들만 들어올 수 있음 

※static method

매개변수는 있지만, 바로 return 되어 반한되어짐
이런 속성을 이용한 것이 static method
여기서는 Objects라는 객체를 통해 requireNonNull이라는 메서드를 사용하고 매개변수가
null인지 체크해서 null이면 NullPointException 이 발생하도록 하는 것 
※ .clone()
생성자에서는 필드에 각각 값을 final로 생성해서 만들었다면 여기서는 .clone을 통해 불변화시키는 모습

 

 

import java.util.Objects;

public class CheeseBurger extends Hamburger {
    public enum CheeseSize { Single, Duuble, King}
    private final CheeseSize cheeseSize;

    public static class Builder extends Hamburger.Builder<Builder> {
        private final CheeseSize cheeseSize;
        public Builder(CheeseSize cheeseSize){
            this.cheeseSize = Objects.requireNonNull(cheeseSize);
        }
        //@Override를 통해 상속되었음을 표시
        @Override public CheeseBurger build() {
            return new CheeseBurger(this);
        }
        @Override protected Builder self() {return this;}
    }

    private CheeseBurger(Builder builder){
        super(builder);
        cheeseSize = builder.cheeseSize;
    }
}

src.zip
0.00MB