Item 2. Builder
์์ฑ์์ ๋งค๊ฐ๋ณ์๊ฐ ๋ง๋ค๋ฉด ๋น๋๋ฅผ ๊ณ ๋ คํ๋ผ
Item 1์์ ์๊ฐ ๋ ์ ์ ํฉํฐ๋ฆฌ ๋ฉ์๋์ public ์์ฑ์ ๋ ๋ค ๊ฐ๊ณ ์๋ ๋จ์ ์ ์ ํ์ ๋งค๊ฐ๋ณ์๊ฐ ๋ง์ ๋ ์ ์ ํ ๋์ํ๊ธฐ ์ด๋ ต๋ค๋ ๊ฒ์ด๋ค. ์ ํ์ ๋งค๊ฐ๋ณ์๊ฐ ๋ง์์ง๋ฉด ๊ทธ๋งํผ ๋๊ฒจ์ผ ํ ๋งค๊ฐ๋ณ์์ ์๋ ๋์ด๋๊ฒ ๋์ด ์ฌ์ฉํ๊ธฐ ๋ถํธํด์ง๋ค. ๋จผ์ ๋น๋ ํจํด์ ์ฌ์ฉํ์ง ์์ ์์๋ค์ ์ดํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
๋น๋๋ฅผ ์ฌ์ฉํ์ง ์์ ์์
์ ํต์ ์ธ ์ ์ธต์ ์์ฑ์ ํจํด(telescoping constructor pattern)
์ฌ๋ฌ ์์ฑ์๋ฅผ ์ ์ํ๊ณ , ๊ฐ๊ฐ์ ์์ฑ์์์๋ ํ์ํ ๋งค๊ฐ๋ณ์๋ง ๋ฐ๋ ๋ฐฉ์์ด๋ค.
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);
}
// ... parameters increase...
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;
}
}
class Example {
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}
๋งค๊ฐ๋ณ์๊ฐ ๋ง์์ง๋ฉด ํด๋น ํ๋ผ๋ฏธํฐ๊ฐ ์ด๋ค ํ๋ ๊ฐ์ ์ ์ฅ๋๋์ง, ๋ช ๊ฐ์ ๋งค๊ฐ๋ณ์๊ฐ ์ด๋ค ์์๋ก ์ ๋ฌ๋์ด์ผ ํ๋์ง ์๊ธฐ ์ด๋ ค์
ํ์ฌ๋ IDE๊ฐ ์๋ณํ๊ธฐ ์ฝ๊ฒ ์ด๋์ ๋ ์ง์ํด์ฃผ๊ธด ํ๋ ๊ฒฐ๊ตญ ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์๋ ์ด์ ์๋ํ์ง ์์ ๊ฐ์ด ์ ๋ฌ๋๊ฑฐ๋ ๋ฐํ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์
์๋ฐ ๋น์ฆ(JavaBeans)
๋งค๊ฐ๋ณ์๊ฐ ์๋ ์์ฑ์๋ก ๊ฐ์ฒด๋ฅผ ๋ง๋ ํ setter ๋ฉ์๋๋ค์ ํธ์ถํ์ฌ ๊ฐ์ ์ค์ ํ๋ ๋ฐฉ์์ด๋ค.
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;
}
// all setter methods...
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}
class Example {
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
๋ช ์์ ์ผ๋ก ํ๋ ์ด๋ฆ์ ํธ์ถํ์ฌ ๊ฐ์ ์ค์ ํ ์ ์์ด ๊ฐ๋ ์ฑ ํฅ์
ํ์ง๋ง ๊ฐ์ฒด ํ๋๋ฅผ ๋ง๋ค๋ ค๋ฉด ๋ฉ์๋๋ฅผ ์ฌ๋ฌ ๊ฐ ํธ์ถํด์ผ ํ๊ณ , ๊ฐ์ฒด๊ฐ ์์ ํ ์์ฑ๋๊ธฐ ์ ๊น์ง๋ ์ผ๊ด์ฑ(consistency)์ด ๋ฌด๋์ง
๋ชจ๋ ํ๋์ ๋ํด setter ๋ฉ์๋๊ฐ ์ด๋ ค์๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ตญ ํด๋์ค์ ๋ถ๋ณ์ฑ์ ๋ณด์ฅํ ์ ์์
๋น๋ ํจํด
๋ณธ๋ฌธ์ ์ฃผ์ ์ธ ๋น๋ ํจํด์ ์์ ๋ ๋ฐฉ์์ ์ฅ์ ๋ง ์ทจํ๊ณ ๋จ์ ์ ๋ณด์ํ ๋ฐฉ์์ผ๋ก, ๋น๋ ํจํด์ด ๋์๋๋ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ๋ค.
ํ์ ๋งค๊ฐ๋ณ์๋ง์ผ๋ก ์์ฑ์(ํน์ ์ ์ ํฉํฐ๋ฆฌ ๋ฉ์๋)๋ฅผ ํธ์ถํ์ฌ ๋น๋ ๊ฐ์ฒด๋ฅผ ์ป์
๋น๋ ๊ฐ์ฒด๊ฐ ์ ๊ณตํ๋ ์ผ์ข ์ ์ธํฐ ๋ฉ์๋๋ค๋ก ์ํ๋ ์ ํ ๋งค๊ฐ๋ณ์๋ค์ ์ค์
๋ง์ง๋ง์ผ๋ก
build()
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ํ์ํ ๊ฐ์ฒด๋ฅผ ์ป์
๋น๋ ๊ฐ์ฒด์ ๋ฉ์๋๋ค์ ๋ชจ๋ this
๋ฅผ ๋ฐํํ๋๋ก ๊ตฌํํ์ฌ ๋ฉ์๋ ์ฐ์(method chaining) ๋ฐฉ์์ ์ฌ์ฉํ ์ ์๋ค.
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;
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
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) {
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;
}
// ๋น๋ ๊ฐ์ฒด๋ฅผ ํตํด NutritionFacts ๊ฐ์ฒด๋ฅผ ์์ฑ
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
}
class Example {
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8) // 1. ํ์ ๋งค๊ฐ๋ณ์๋ง์ผ๋ก ๋น๋ ๊ฐ์ฒด๋ฅผ ์ป์
.calories(100) // 2. ๋น๋ ๊ฐ์ฒด๊ฐ ์ ๊ณตํ๋ ์ผ์ข
์ ์ธํฐ ๋ฉ์๋๋ค๋ก ์ํ๋ ์ ํ ๋งค๊ฐ๋ณ์๋ค์ ์ค์
.sodium(35)
.carbohydrate(27)
.build(); // 3. ๋ง์ง๋ง์ผ๋ก build() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ํ์ํ ๊ฐ์ฒด๋ฅผ ์ป์
}
}
๋น๋ ํจํด์ ํนํ ๊ณ์ธต์ ์ผ๋ก ์ค๊ณ๋ ํด๋์ค์ ํจ๊ป ์ฐ๊ธฐ์ ์ข๋ค. ์์ ๊ณ์ธต์ ์ถ์ ๋น๋๊ฐ ํฌํจ๋ ํด๋์ค๋ก ์ ์ํ๊ณ , ํ์ ํด๋์ค๋ค์ ์ถ์ ๋น๋๋ฅผ ์์๋ฐ์ ๊ตฌํํ๋๋ก ํ๋ฉด ๋๋ค.
abstract class Pizza {
final Set<Topping> toppings;
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
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();
// !!ํ์ ํด๋์ค๋ ์ด ๋ฉ์๋๋ฅผ `this` ๋ฐํํ๋๋ก ๊ตฌํ ํ์
protected abstract T self();
}
}
class OguPizza extends Pizza {
private final Size size;
private OguPizza(Builder builder) {
super(builder);
size = builder.size;
}
public enum Size {SMALL, MEDIUM, LARGE}
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public OguPizza build() { // ์์ ๋ฐ์ ํด๋์ค๊ฐ ์๋ OguPizza๋ฅผ ๋ฐํํ๋๋ก ์ค๋ฒ๋ผ์ด๋ฉ
return new OguPizza(this);
}
// ํ์ ํด๋์ค์์๋ ๋ฐํ ํ์
์ด OguPizza.Builder๊ฐ ๋๋๋ก ์ค๋ฒ๋ผ์ด๋ฉ
@Override
protected Builder self() {
return this;
}
}
}
class Example {
public static void main(String[] args) {
OguPizza pizza = new OguPizza.Builder(OguPizza.Size.SMALL)
.addTopping(Pizza.Topping.HAM)
.addTopping(Pizza.Topping.ONION)
.build();
}
}
์ ์ฝ๋๋ฅผ ๋ณด๋ฉด OguPizza
ํด๋์ค๋ Pizza
ํด๋์ค์ ๋น๋๋ฅผ ์์๋ฐ์ ๊ตฌํํ๋ฏ๋ก Pizza
ํด๋์ค์ ๋น๋๊ฐ ์ ๊ณตํ๋ ๋ฉ์๋๋ค์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
ํ์ ํด๋์ค์์ ๊ตฌํ๋ build()
๋ฉ์๋๋ ์์ ํด๋์ค์ ๋ฉ์๋๊ฐ ์ ์ํ ๋ฐํ ํ์
(Pizza
)์ด ์๋
ํด๋นํ๋ ํ์ ํด๋์ค(OguPizza
)๋ฅผ ๋ฐํํ๋๋ก ์ค๋ฒ๋ผ์ด๋ฉํ์๋ค.(= ๊ณต๋ณ ๋ฐํ ํ์ดํ(covariant return typing)))
์ด๋ ๊ฒ ๊ตฌํํ๋ฉด ํด๋ผ์ด์ธํธ๋ ํ๋ณํ์ ์ ๊ฒฝ์ฐ์ง ์๊ณ ๋ ํ์ ํด๋์ค์ ๋น๋๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
๋น๋ ํจํด์ ๋จ์
๋น๋ ํจํด์๋ ์๋์ ๊ฐ์ ๋จ์ ์ด ์กด์ฌํ๋ค.
์์์ ํ์ธํ ์ ์๋ฏ์ด ๋น๋ ํจํด์ ์ฌ์ฉํ๋ฉด ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ๋ง๋๋๋ฐ ๋ง์ ์์ ์ฝ๋ ์ฆ๊ฐ
์ฑ๋ฅ์ด ์ค์ํ ์ํฉ์์ ๋น๋ ํธ์ถ ๋๋ง๋ค ์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์ ํ๊ฐ ๋ฐ์ํ ์ ์์
ํ์ง๋ง ์ฒ๋ฆฌํด์ผํ ๋งค๊ฐ๋ณ์๊ฐ ๋ง๊ณ ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์น์ง ์๋ ์ํฉ์์๋ ๋ณดํต ๋น๋ ํจํด์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
Lombok @Builder
Lombok์ @Builder
์ ๋ํ
์ด์
์ ์ฌ์ฉํ๋ฉด ๋น๋ ํจํด์ ๊ตฌํํ๊ธฐ ์ํด ํ์ํ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์์ฑํด์ฃผ๊ธฐ ๋๋ฌธ์ ๋จ์ ์ด ๋ ์ ์๋ ์ฝ๋๋์ ์ฆ๊ฐ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
// dto
@Getter
public class OrderSaveRequest {
private BigDecimal totalAmount;
public OrderInfo toEntity(User user) {
OrderInfo orderInfo = OrderInfo.builder()
.user(user)
.totalAmount(this.totalAmount)
.build();
return orderInfo;
}
}
// entity
@Getter
@Builder
public class OrderInfo {
private User user;
@Builder.Default
private String orderNumber = getUniqueOrderNumber();
private BigDecimal totalAmount;
@Builder.Default
private LocalDateTime orderDate = LocalDateTime.now();
private static String getUniqueOrderNumber() {
return UUID.randomUUID().toString();
}
}
Last updated
Was this helpful?