일반적으로 클래스의 인스턴스화 하는 방법 중 하나는 생성자를 사용하는 것 이다.
public class Item2 {
NuitionFacts nutritionFacts = new NutritionFacts(240,5,100,0,27);
}
class NutritionFacts{
private final int servingSize; //필수
private final int servings; //필수
private final int calories; //선택
private final int fat; //선택
private final int sodium; //선택
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
}
}
생성자를 통한 인스턴스화는 매개변수가 많아지면 코드를 작성하거나 읽기 어렵다
다른 방법으로는 자바빈즈 패턴이 있다.
class NutritionFacts{
private int servingSize; //필수
private int servings; //필수
private int calories; //선택
private int fat; //선택
private int sodium; //선택
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;
}
}
NutritionFacts nutritionFacts = new NutritionFacts();
nutritionFacts.setServingSize(240);
nutritionFacts.setServings(5);
nutritionFacts.setCalories(100);
nutritionFacts.setFat(0);
nutritionFacts.setSodium(27);
자바빈즈패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성 되기 전까지 일관성이 무너진 상태에 놓이게 된다
이러한 문제로 대안으로 나온게 빌더 패턴(Builder pattern)이다.
class NutritionFacts{
private final int servingSize; //필수
private final int servings; //필수
private final int calories; //선택
private final int fat; //선택
private final int sodium; //선택
public static class Builder{
//필수
private int servingSize;
private int servings;
//선택 - 기본값으로 초기화 해야한다
private int calories = 0;
private int fat = 0;
private int sodium = 0;
public Builder(int servingSize, int servings){
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val){
if(val < 0) throw new IllegalArgumentException();
calories = val;
return this;
}
public Builder fat(int val){
fat = val;
return this;
}
public Builder sodium(int val){
sodium = 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;
}
}
public class Item2 {
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).calories(100).sodium(35).build();
}
빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출 할 수 있다.
또한 잘못된 매개변수를 최대한 일찍 발견하려면 빌더의 생성자와 매개변수에서 검사 할 수 있다.
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다
public class Item2 {
NyPizza nyPizza = new NyPizza.Builder(NyPizza.Size.SMALL).addToping(Pizza.Topping.HAM).addToping(Pizza.Topping.ONION).build();
}
//피자
abstract class Pizza{
public enum Topping{HAM, MUSHROOM, ONION, PEPPER}
private final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>>{ //Builder<T> 상속받는 class만 접근 가능한 재귀적 제너럴 타입
//Topping이라는 Enum을 다루는 Set으로 비어있는 Set을 반환
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addToping(Topping topping){
toppings.add(topping);
return self();
}
abstract Pizza build();
//하위 클래스는 이 메서드를 재정의하여
//this를 반환하도록 해야 한다.
protected abstract T self();
}
Pizza(Builder<?> builder){
toppings = builder.toppings;
}
}
//뉴욕피자
class NyPizza extends Pizza {
public enum Size {SMALL, MEDIUM, LARGE}
private final Size size;
public static class Builder extends Pizza.Builder<Builder>{
private final Size size;
public Builder(Size size){
this.size = size;
}
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder){
super(builder);
size = builder.size;
}
}
Pizza.Builder 클래스는 재귀적 타입을 이용하는 제네릭 타입이다.
추상 메서드인 self를 추가해 하위 클래스에서는 형변환하지 않고도 메서드 연쇄를 지원 가능하다.
각 하위 클래스의 빌더가 정의한 build 메서드는 해당하는 구체 하위 클래스를 반환하도록 선언한다. NyPizza.Builder는 NyPizza를 반환한다.
※ @Override한 구현 메서드에서 반환형이 달라도 상속 관계면 가능하다는걸 알게 되었음
최근에는 Lombok 라이브러리를 통해서 @Builder를 사용했는데, 구체적인 원리를 알고 쓰는 기분이다. Lombok을 사용하여 자식클래스에서 부모클래스 멤버변수 build를 사용하려면 상속, 상속 받는 클래스에 @SuperBuilder를 사용하면 된다! 굳이 builder 클래스를 만들필요 없음