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 ๋ฉ”์„œ๋“œ๊ฐ€ ์—ด๋ ค์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ๊ตญ ํด๋ž˜์Šค์˜ ๋ถˆ๋ณ€์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†์Œ

๋นŒ๋” ํŒจํ„ด

๋ณธ๋ฌธ์˜ ์ฃผ์ œ์ธ ๋นŒ๋” ํŒจํ„ด์€ ์œ„์˜ ๋‘ ๋ฐฉ์‹์˜ ์žฅ์ ๋งŒ ์ทจํ•˜๊ณ  ๋‹จ์ ์€ ๋ณด์™„ํ•œ ๋ฐฉ์‹์œผ๋กœ, ๋นŒ๋” ํŒจํ„ด์ด ๋™์ž‘๋˜๋Š” ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ํ•„์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ์œผ๋กœ ์ƒ์„ฑ์ž(ํ˜น์€ ์ •์  ํŒฉํ„ฐ๋ฆฌ ๋ฉ”์„œ๋“œ)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋นŒ๋” ๊ฐ์ฒด๋ฅผ ์–ป์Œ

  2. ๋นŒ๋” ๊ฐ์ฒด๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ผ์ข…์˜ ์„ธํ„ฐ ๋ฉ”์„œ๋“œ๋“ค๋กœ ์›ํ•˜๋Š” ์„ ํƒ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์„ค์ •

  3. ๋งˆ์ง€๋ง‰์œผ๋กœ 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?