ITEM 2: Builder Pattern

์ƒ์„ฑ์ž์™€ ์ •์  ํŒฉํ† ๋ฆฌ๋Š” ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๋งŽ์„ ๋•Œ ์ ์ ˆํžˆ ๋Œ€์‘ํ•˜๊ธฐ ์–ด๋ ต๋‹ค. ์„ ํƒ์  ๋งค๊ฐœ ๋ณ€์ˆ˜๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•˜๋Š” ์ƒ์„ฑ์ž ํŒจํ„ด์— ๋Œ€ํ•ด์„œ ์‚ดํŽด ๋ณผ ๊ฒƒ์ด๋‹ค.

์ƒ์„ฑ์ž ํŒจํ„ด 1. ์ ์ธต์  ์ƒ์„ฑ์ž ํŒจํ„ด

์ ์ธต์  ์ƒ์„ฑ์ž ํŒจํ„ด(telescoping constructor pattern)์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•„์ˆ˜ ์ธ์ž๋ฅผ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ฅผ ์ •์˜ํ•œ ํ›„, ์„ ํƒ์ ์ธ์ž๋ฅผ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•ด๊ฐ€๋ฉฐ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

public class Item {
  private final String itemCd; // ํ•„์ˆ˜
  private final String itemNm; // ํ•„์ˆ˜
  private final String ctgId;  // ํ•„์ˆ˜
  private final BigDecimal price; // ์„ ํƒ
  private final String sellTypeCd; // ์„ ํƒ

  public Item(String itemCd, String itemNm, String ctgId){
    this(itemCd, itemNm, ctgId, 0);
  }

  public Item(String itemCd, String itemNm, String ctgId, BigDecimal price){
    this(itemCd, itemNm, ctgId, price, "10");
  }

  public Item(String itemCd, String itemNm, String ctgId, BigDecimal price, String sellTypeCd){
    this.itemCd = itemCd;
    this.itemNm = itemNm;
    this.ctgId = ctgId;
    this.price = price;
    this.sellTypeCd = sellTypeCd;
  }

}

์˜ˆ์‹œ์—์„œ๋Š” ์ธ์ž๊ฐ€ 5๊ฐœ๋ผ ๊ฐ„๋‹จํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๋” ๋Š˜์–ด๋‚  ์ˆ˜๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง€๊ณ , ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ฒŒ ๋œ๋‹ค.

์ƒ์„ฑ์ž ํŒจํ„ด 2. JavaBeans Pattern

์ž๋ฐ”๋นˆ์ฆˆ ํŒจํ„ด์€ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” ์ƒ์„ฑ์ž๋กœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“  ํ›„ setter ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ์›ํ•˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

public class Item {
    private String itemCd; // ํ•„์ˆ˜
    private String itemNm; // ํ•„์ˆ˜
    private String ctgId;  // ํ•„์ˆ˜
    private BigDecimal price; // ์„ ํƒ
    private String sellTypeCd; // ์„ ํƒ

    public Item() {}

    public void setItemCd(String itemCd){ this.itemCd = itemCd; }
    public void setItemNm(String itemNm){ this.itemNm = itemNm; }
    public void setCtgId(String ctgId){ this.ctgId = ctgId; }
    public void setPrice(BigDecimal price){ this.price = price; }
    public void setSellTypeCd(String sellTypeCd){ this.sellTypeCd = sellTypeCd; } 

}
Item item = new Item();
item.setItemCd("12345678");
item.setItemNm("Effective Java 3/E");
item.setCtgId("9999");
item.setPrice("36000");
item.setSellTypeCd("20");

์ž๋ฐ”๋นˆ์ฆˆ ํŒจํ„ด์€ ์ ์ธต์  ์ƒ์„ฑ์ž ํŒจํ„ด์˜ ๋‹จ์ ์„ ๋ณด์™„ํ•ด ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ์ด ์‰ฝ๊ณ , ๋” ๊ฐ€๋…์„ฑ์ด ์ข‹์•„์กŒ๋‹ค.

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

์ƒ์„ฑ์ž ํŒจํ„ด 3. Builder Pattern

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

public class Item {
    private final String itemCd; // ํ•„์ˆ˜
    private final String itemNm; // ํ•„์ˆ˜
    private final String ctgId;  // ํ•„์ˆ˜
    private final BigDecimal price; // ์„ ํƒ
    private final String sellTypeCd; // ์„ ํƒ

    public static class Builder {
        private final String itemCd; // ํ•„์ˆ˜
        private final String itemNm; // ํ•„์ˆ˜
        private final String ctgId;  // ํ•„์ˆ˜

        // ์„ ํƒ์  ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” default ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”
        private BigDecimal price = BigDecimal.ZERO; 
        private String sellTypeCd = "00";

        public Builder(String itemCd, String itemNm, String ctgId) {
            this.itemCd = itemCd;
            this.itemNm = itemNm;
            this.ctgId = ctgId;
        }

        public Builder price(BigDecimal price) {
            this.price = price;
            return this;
        }

        public Builder sellTypeCd(String sellTypeCd) {
            this.sellTypeCd = sellTypeCd;
            return this;
        }

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

    private Item(Builder builder) {
        itemCd = builder.itemCd;
        itemNm = builder.itemNm;
        ctgId = builder.ctgId;
        price = builder.price;
        sellTypeCd = builder.sellTypeCd;
    }

}
Item item = new Item.Builder("12345678", "Effective Java 3/E", "9999").price(36000).sellTypeCd("90").build();

ํด๋ผ์ด์–ธํŠธ๋Š” ํ•„์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ์œผ๋กœ ์ƒ์„ฑ์ž๋ฅผ ํ˜ธ์ถœํ•ด ๋นŒ๋” ๊ฐ์ฒด๋ฅผ ์–ป๊ณ , ๋นŒ๋” ๊ฐ์ฒด๊ฐ€ ์ œ๊ณตํ•˜๋Š” setter ๋ฉ”์„œ๋“œ๋“ค๋กœ ์›ํ•˜๋Š” ์„ ํƒ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋Š” build() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ํ•„์š”ํ•œ ๊ฐ์ฒด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์—ฐ์‡„์ ์œผ๋กœ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•์„ fluent API or method chaining์ด๋ผ ํ•œ๋‹ค.

๋ถˆ๋ณ€ : ์–ด๋– ํ•œ ๋ณ€๊ฒฝ๋„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ String ๊ฐ์ฒด๋Š” ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด์ง€๋ฉด ์ ˆ๋Œ€ ๊ฐ’์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†๋Š” ๋ถˆ๋ณ€ ๊ฐ์ฒด

๋ถˆ๋ณ€์‹ : ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ(ํ˜น์€ ์ •ํ•ด์ง„ ๊ธฐ๊ฐ„) ๋ฐ˜๋“œ์‹œ ๋งŒ์กฑํ•ด์•ผํ•˜๋Š” ์กฐ๊ฑด์„ ๋งํ•œ๋‹ค. ๋ณ€๊ฒฝ์„ ํ—ˆ์šฉํ•  ์ˆ˜ ๋Š” ์ž‡์œผ๋‚˜, ์ฃผ์–ด์ง„ ์กฐ๊ฑด ๋‚ด์—์„œ๋งŒ ํ—ˆ์šฉํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.

๋ถˆ๋ณ€์‹์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋นŒ๋”๋กœ ๋ถ€ํ„ฐ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ณต์‚ฌํ•œ ํ›„ ํ•ด๋‹น ๊ฐ์ฒด ํ•„๋“œ๋„ ๊ฒ€์‚ฌํ•ด์•ผํ•œ๋‹ค.(item50) ๊ฒ€์‚ฌ์‹œ ์ž˜๋ชป๋œ ์ ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด ์–ด๋–ค ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ž˜๋ชป๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ๋ฉ”์„ธ์ง€๋ฅผ ๋‹ด์•„ IllegalArgumentException (item75) ์˜ค๋ฅ˜ ๋ฐœ์ƒ์„ ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

๊ณ„์ธต์ ์œผ๋กœ ์„ค๊ณ„๋œ ํด๋ž˜์Šค

๋นŒ๋” ํŒจํ„ด์€ ๊ณ„์ธต์ ์œผ๋กœ ์„ค๊ณ„๋œ ํด๋ž˜์Šค์™€ ์‚ฌ์šฉํ•˜๊ธฐ์— ์ข‹๋‹ค.

public abstract class Allnco {
  public enum ApiType { ADD_ITEM, UPDATE_ITEM, UPDATE_IMAGE, UPDATE_PRC }
  final Set<ApiType> apiTypes;

  abstract static class Builder<T extends Builder<T>> {
    EnumSet<ApiType> apiTypes = EnumSet.noneOf(ApiType.class);

    public T addApiType(ApiType apiType){
      apiTypes.add(Objects.requireNonNull(apiType));
      return self();
    }

    abstract Allnco build();

    // ํ•˜์œ„ ํด๋ž˜์Šค๋Š” ์ด ๋ฉ”์„œ๋“œ๋ฅผ overridingํ•ด "this"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ตฌํ˜„ํ•ด์•ผํ•จ.
    protected abstract T self();
  }

  Allnco(Builder<?> builder){
    apiTypes = builder.apiTypes.clone();
  }
}

์—ฌ๊ธฐ์„œ Allnco.Builder ํด๋ž˜์Šค๋Š” ์žฌ๊ท€์  ํƒ€์ž… ํ•œ์ •์„ ์ด์šฉํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค. ์—ฌ๊ธฐ์— ์ถ”๊ฐ€์ ์œผ๋กœ ์ถ”์ƒ ๋ฉ”์„œ๋“œ์ธ self() ๋ฅผ ์ถ”๊ฐ€ํ•ด ํ•˜์œ„ ํด๋ž˜์Šค์—์„œ ํ˜• ๋ณ€ํ™˜ ํ•˜์ง€ ์•Š๊ณ ๋„ method chaining์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

public class Gmarket extends Allnco{
    public enum Chnl { ONLINE, OUTLET, MART, DEPARTMENT, BUYING }
    private final Chnl chnl; // final -> immutable

    public static class Builder extends Allnco.Builder<Builder> {
        private final Chnl chnl;

        public Builder(Chnl chnl){
            this.chnl = Objects.requireNonNull(chnl);
        }

        @Override
        public Gmarket build(){
            return new Gmarket(this);
        }

        @Override
        protected Builder self(){
            return this;
        }
    }
    private Gmarket(Builder builder){
        super(builder);
        chnl = builder.chnl;
    }
}
public class Naver extends Allnco{
    private final boolean isHapi;

    public static class Builder extends Allnco.Builder<Builder> {
        public boolean isHapi = false;

        public Builder connectToHapi(){
            isHapi = true;
            return this;
        }

        @Override
        public Naver build(){
            return new Naver(this);
        }

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

    }
    private Naver(Builder builder){
        super(builder);
        isHapi = builder.isHapi;
    }
}
Gmarket gmarket = new Gmarket.Builder(Gmarket.Chnl.MART).addApiType(Gmarket.ApiType.UPDATE_ITEM).addApiType(Gmarket.ApiType.UPDATE_PRC).build();

Naver naver = new Naver.Builder().addApiType(Naver.ApiType.ADD_ITEM).connectToHapi().build();

๊ฐ๊ฐ์˜ ํ•˜์œ„ ํด๋ž˜์Šค์˜ ๋นŒ๋”๊ฐ€ ์ •์˜ํ•œ build ๋ฉ”์„œ๋“œ๋Š” ํ•ด๋‹น ํ•˜์œ„ ํด๋ž˜์Šค(Naver, Gmarket)์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋˜์–ด์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜์œ„ ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์ƒ์œ„ ํด๋ž˜์Šค๊ฐ€ ์ •์˜ํ•œ ๋ฆฌํ„ด ํƒ€์ž…์ด ์•„๋‹Œ, ๊ทธ ํ•˜์œ„ ํƒ€์ž…์„ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ์„ Convariant return typing(๊ณต๋ณ€ ๋ฐ˜ํ™˜ ํƒ€์ดํ•‘)์ด๋ผ ํ•œ๋‹ค. ์ด ๊ธฐ๋Šฅ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ˜•๋ณ€ํ™˜์— ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š๊ณ  ๋นŒ๋”๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฒฐ๋ก 

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

ํ•˜์ง€๋งŒ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด, ๊ทธ์— ์•ž์„œ ๋นŒ๋”๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค. ๋˜ํ•œ, ์„ฑ๋Šฅ์— ๋ฏผ๊ฐํ•œ ์ƒํ™ฉ์—์„œ๋Š” ๋นŒ๋” ์ƒ์„ฑ ๋น„์šฉ์ด ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ 4๊ฐœ ์ด์ƒ์ด ๋˜์–ด์•ผ ๊ฐ’์–ด์น˜๋ฅผ ํ•œ๋‹ค.

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

Last updated