Skip to main content

๊ทœ์น™2 : ์ƒ์„ฑ์ž ์ธ์ž๊ฐ€ ๋งŽ์„ ๋•Œ๋Š” Builder ํŒจํ„ด ์ ์šฉ์„ ๊ณ ๋ คํ•˜๋ผ

2024๋…„ 7์›” 4์ผAbout 3 minJavacrashcoursejavajdkjdk8

๊ทœ์น™2 : ์ƒ์„ฑ์ž ์ธ์ž๊ฐ€ ๋งŽ์„ ๋•Œ๋Š” Builder ํŒจํ„ด ์ ์šฉ์„ ๊ณ ๋ คํ•˜๋ผ ๊ด€๋ จ

๋ชฉ์ฐจ

Effective Java

๊ฐ์ฒด์˜ ์ƒ์„ฑ๊ณผ ์‚ญ์ œ | ์–‘๋ด‰์ˆ˜ ๋ธ”๋กœ๊ทธ
๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•˜๋Š” ์‹œ์ ๊ณผ ๊ทธ ๋ฐฉ๋ฒ•, ๊ฐ์ฒด ์ƒ์„ฑ์„ ํ”ผํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์™€ ๊ทธ ๋ฐฉ๋ฒ•, ์ ์ ˆํ•œ ์ˆœ๊ฐ„์— ๊ฐ์ฒด๊ฐ€ ์‚ญ์ œ๋˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  ์‚ญ์ œ ์ „์— ๋ฐ˜๋“œ์‹œ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•˜๋Š” ์ฒญ์†Œ ์ž‘์—…๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณธ๋‹ค.

Consider a builder when faced with many constructor parameters

์„ ํƒ์  ์ธ์ž๊ฐ€ ๋งŽ์€ ์ƒํ™ฉ์—์„œ ์–ด๋–ค ์ƒ์„ฑ์ž๋‚˜ ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๊ฐ€ ์ ํ•ฉํ• ๊นŒ?

์ ์ธต์  ์ƒ์„ฑ์ž ํŒจํ„ด (telescoping constructor pattern)

ํ•„์ˆ˜ ์ธ์ž๋งŒ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ฅผ ํ•˜๋‚˜ ์ •์˜ํ•˜๊ณ , ์„ ํƒ์  ์ธ์ž๋ฅผ ํ•˜๋‚˜ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ฑฐ๊ธฐ์— ๋‘๊ฐœ์˜ ์„ ํƒ์  ์ธ์ž๋ฅผ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์‹์œผ๋กœ ์ƒ์„ฑ์ž๋“ค์„ ์Œ“์•„ ์˜ฌ๋ฆฌ๋“ฏ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

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(servingSizem servings, 0);
  }

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

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

  public NutritionFacts(int servingSize, int servings, int calories, int fat,
              int sodium){
    this(servingSizem 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; 
  }
}

์ด ๋ฐฉ์‹์€ ์ธ์ž ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์ง€๊ณ , ๋ฌด์—‡๋ณด๋‹ค ์ฝ๊ธฐ ์–ด๋ ค์šด ์ฝ”๋“œ๊ฐ€ ๋˜๊ณ  ๋งŒ๋‹ค. ๋Œ€์ฒด ๊ทธ ๋งŽ์€ ์ธ์ž๊ฐ€ ๋ฌด์Šจ ๊ฐ’์ธ์ง€ ์•Œ ์ˆ˜ ์—†๊ฒŒ ๋˜๊ณ , ๊ทธ ์˜๋ฏธ๋ฅผ ์•Œ๋ ค๋ฉด ์ธ์ž๋ฅผ ์ฃผ์˜๊นŠ๊ฒŒ ์„ธ์–ด๋ณด์•„์•ผ ํ•œ๋‹ค.

์ž๋ฐ”๋นˆ ํŒจํ„ด

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() {}

  //์„ค์ •์ž(setter)
  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);

๊ทธ๋Ÿฌ๋‚˜ ์ž๋ฐ”๋นˆ ํŒจํ„ด์—๋Š” ์‹ฌ๊ฐํ•œ ๋‹จ์ ์ด ์žˆ๋‹ค. 1ํšŒ์˜ ํ•จ์ˆ˜ ํ˜ธ์ถœ๋กœ ๊ฐ์ฒด ์ƒ์„ฑ์„ ๋๋‚ผ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ๊ฐ์ฒด ์ผ๊ด€์„ฑ์ด ์ผ์‹œ์ ์œผ๋กœ ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋˜ํ•œ ์ž๋ฐ”๋นˆ ํŒจํ„ด์œผ๋กœ๋Š” ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

๋นŒ๋” (Builder) ํŒจํ„ด

์ ์ธต์  ์ƒ์„ฑ์ž ํŒจํ„ด์˜ ์•ˆ์ „์„ฑ๊ณผ ์ž๋ฐ”๋นˆ ํŒจํ„ด์˜ ๊ฐ€๋…์„ฑ์„ ๊ฒฐํ•ฉํ•œ ํŒจํ„ด์ด๋‹ค.

ํ•„์š”ํ•œ ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜๋Š” ๋Œ€์‹ , ํด๋ผ์ด์–ธํŠธ๋Š” ๋จผ์ € ํ•„์ˆ˜ ์ธ์ž๋“ค์„ ์ƒ์„ฑ์ž์—(๋˜๋Š” ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ์—) ์ „๋ถ€ ์ „๋‹ฌํ•˜์—ฌ ๋นŒ๋” ๊ฐ์ฒด(builder object)๋ฅผ ๋งŒ๋“ ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋นŒ๋” ๊ฐ์ฒด์— ์ •์˜๋œ ์„ค์ • ๋ฉ”์„œ๋“œ๋“ค์„ ํ˜ธ์ถœํ•˜์—ฌ ์„ ํƒ์  ์ธ์ž๋“ค์„ ์ถ”๊ฐ€ํ•ด ๋‚˜๊ฐ„๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ ์•„๋ฌด๋Ÿฐ ์ธ์ž ์—†์ด build ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ๋นŒ๋” ํด๋ž˜์Šค๋Š” ๋นŒ๋”๊ฐ€ ๋งŒ๋“œ๋Š” ๊ฐ์ฒด ํด๋ž˜์Šค์˜ ์ •์  ๋ฉค๋ฒ„ ํด๋ž˜์Šค๋กœ ์ •์˜ํ•œ๋‹ค.

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;

  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 carbohydrate = 0;
    private int sodium = 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 carbohydrate(int val) {
      carbohydrate = val;
      return this;
    }

    public Builder sodium(int val) {
      sodium = val;
      return this;
    }

    public NutritionFacts build() {
      return new NutritionFacts(this);
    }
  }
}

NutritionFacts ๊ฐ์ฒด๊ฐ€ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์‚ฌ์‹ค, ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์ธ์ž์˜ ๊ธฐ๋ณธ๊ฐ’์ด ํ•œ๊ณณ์— ๋ชจ์—ฌ ์žˆ๋‹ค๋Š” ๊ฒƒ์— ์œ ์˜ํ•ด๋ผ. ๋นŒ๋”์— ์ •์˜๋œ ์„ค์ • ๋ฉ”์„œ๋“œ๋Š” ๋นŒ๋” ๊ฐ์ฒด ์ž์‹ ์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ์„ค์ • ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๋Š” ๊ณ„์† ์ด์–ด์„œ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

NutirtionFacts cocaCola = new NutritionFacts.Builder(240,8)
  .calories(100)
  .sodium(35)
  .carbohydrate(27)
  .build();

๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ๋นŒ๋”ํŒจํ„ด์—์„œ ๋ถˆ๋ณ€์‹์„ ๊ฒ€์‚ฌํ•œ๋‹ค๋ฉด ์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ๋นŒ๋” ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ๋ณต์‚ฌ ํ•œ ํ›„์— ์ฒดํฌํ•ด๋ผ.

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;

  private NutritionFacts(Builder builder) {
    servingSize = builder.servingSize;
    if (servingSize > 0) {
      throw new IllegalArgumentException();
    }
    ...
  }

3rd edition ์ถ”๊ฐ€๋œ ๋นŒ๋”ํŒจํ„ด ์˜ˆ์ œ

import java.util.EnumSet;
import java.util.Objects;
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(Objects.requireNonNull(topping));
      return self();
    }

    abstract Pizza build();

    // Subclasses must override this method to return "this"
    protected abstract T self();
  }

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

Pizza ์ถ”์ƒ ํด๋ž˜์Šค์™€ ๊ทธ์•ˆ์— Builder ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

import java.util.Objects;

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
    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; // default

    public Builder sauceInside() {
      sauceInside = true;
      return this;
    }

    @Override
    Calzone build() {
      return new Calzone(this);
    }

    @Override
    protected Builder self() {
      return this;
    }
  }

  private Calzone(Builder builder) {
    super(builder);
    sauceInside = builder.sauceInside;
  }
}

์ฃผ๋ชฉํ•ด์•ผ ๋  ๋ถ€๋ถ„์€ Pizza ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•œ NyPizza, Calzone ํด๋ž˜์Šค์—์„œ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•œ build ๋ฉ”์„œ๋“œ return type์ด ์ž๊ธฐ ์ž์‹ ์ด๋‹ค(Pizza๊ฐ€ ์•„๋‹ˆ๋ผ). ์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ์‚ฌ์šฉํ•  ๋•Œ ํƒ€์ž… ์บ์ŠคํŒ…์„ ๋”ฐ๋กœ ์•ˆํ•ด์ค˜๋„ ๋œ๋‹ค.

public class BuilderMain {
    public static void main(String[] args) {
        NyPizza pizza = new NyPizza.Builder(NyPizza.Size.SMALL)
            .addTopping(Pizza.Topping.SAUSAGE)
            .addTopping(Pizza.Topping.ONION)
      .build();

        Calzone calzone = new Calzone.Builder()
      .addTopping(Pizza.Topping.HAM)
      .sauceInside()
      .build();
    }
}

์š”์•ฝ

๋นŒ๋” ํŒจํ„ด์€ ์ธ์ž๊ฐ€ ๋งŽ์€ ์ƒ์„ฑ์ž(4๊ฐœ ์ด์ƒ)๋‚˜ ์ •์  ํŒฉํ† ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ํด๋ž˜์Šค๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ, ํŠนํžˆ ๋Œ€๋ถ€๋ถ„์˜ ์ธ์ž๊ฐ€ ์„ ํƒ์  ์ธ์ž์ธ ์ƒํ™ฉ์— ์œ ์šฉํ•˜๋‹ค.