영양정보를 표현하는 클래스가 있는 경우
영양정보에는 여러가지 필수 사항 + 여러가지 선택 사항이 존재한다.
public class NutritionFacts {
private final int servingSize; // (ml, 1회 제공량) 필수
private final int servings; // (회, 총 제공량) 필수
private final int calories; // (1회 제공량당) 선택
private final int fat; // (g/1회 제공량) 선택
private final int sodium; // (mg/1회 제공량) 선택
private final int carbohydrate; // (g/1회 제공량) 선택
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;
}
}
해당 방식을 점층적 생성자 패턴(telescoping constructor pattern)
이라고 한다.
필수매개와 선택 매개를 몇개 받는 생성자, 선택을 모두 받는 생성자까지 늘려가는 방식이라고 한다.
해당 클래스의 인스턴를 만들려면
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
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 val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
스레드 안전성 문제 때문에 추가 작업이 필요하다
public class Main {
public static void main(String[] args) {
NutritionFacts facts = new NutritionFacts();
Thread t1 = new Thread(() -> {
facts.setServingSize(240);
facts.setServings(8);
});
Thread t2 = new Thread(() -> {
facts.setCalories(100);
facts.setSodium(35);
});
t1.start();
t2.start();
}
}
같은 소스에서 다른 스레드에서 set을 동시적으로 수행하는 경우 무슨 값이 객체에 저장될지는 알 수가 없다.
freezing()
이 가능하다고 한다.안정성
가독성
예시
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 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) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
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;
}
}
---
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
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(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
Pizza
클래스라는 파자의 기본적인 구조를 정의하는 추상 클래스이다.public 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 = Objects.requireNonNull(size);
}
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false; // 기본값
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
public Calzone build() {
return new Calzone(this);
}
@Override
protected Builder self() {
return this;
}
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
Pizza
클래스는 추상 클래스인데, Builder
내부 클래스를 가진다.Builder
는 제네릭 타입을 사용해 타입 안정성을 보장하고 서브 클래스의 빌더와 연결된다.abstract static class Builder<T extends Builder<T>> {
self()
의 경우 하위 클래스의 빌더가 상위 클래스의 빌더 메서드를 호출한 뒤에도, 연쇄적으로 자신의 메서드를 호출가능하도록 함.public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self(); <- 요 부분을 보면 addTopping 은 상위 클래스의 메서드이지만, 재정의된 self() 를 통해 하위 클래스타입으로 되돌려 받을 수 있음.
}