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

Was this helpful?