본문 바로가기

Language/Java

[Effective Java] builder pattern

** effective java 공부내용 입니다.

 

매개변수가 많을때에는 builder pattern 사용을 고려하자.

 

python이나 scala의 경우 파라미터가 많은 경우 아래의 예시처럼 named optional pattern 사용이 가능하다.

class NutritionFacts:
    def __init__(self, servingSize, servings, calories=0, fat=0, sodium=0, carbohydrates=0):
        self.servingSize = servingSize
        self.servings = servings
        self.calories = calories
        self.fat = fat
        self.sodium = sodium
        self.carbohydrates = carbohydrates

 

java의 경우에는 매개변수가 많으면 어쩔 수 없이 아래의 코드 예시와 같이 생성자를 overloading하여 작성하게된다. 이는 코드가 길어지고 읽기어려우며 각 값의 위치가 헷갈릴 수 있다.

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;

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

    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);
    }

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

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}

필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 builder 객체를 얻는다. 그런 다음 builder 객체가 제공하는 setter 메서드를 통해 원하는 매개변수를 설정한다.

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

    public 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 val) {this.calories = val; return this;}
        public Builder fat(int val) {this.fat = val; return this;}
        public Builder sodium(int val) {this.sodium = val; return this;}
        public Builder carbohydrate(int val) {this.carbohydrate = val; return this;}
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    public static void main(String[] args) {
        NutritionFacts foods = new NutritionFacts.Builder(1, 2).calories(3).sodium(4).carbohydrate(5).build();
    }

Builder static class에서는 method chaining을 통해 값을 넘길 수 있도록 자기자신을 반환하고, 마지막으로 parent class는 build를 통해 값을 초기화한다.

 

추가로 아래 추상 클래스를 통해 빌더를 만들 수 있는데 계층적으로 만들게되는 클래스에대해 고민해보자.

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

public 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(topping);
            return self();
        }

        abstract Pizza build();
        protected abstract T self();
    }

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

}