λ„μ„œ πŸ“š/Effective Java

[Effective Java] item 02. μƒμ„±μžμ— λ§€κ°œλ³€μˆ˜κ°€ λ§Žλ‹€λ©΄ λΉŒλ”λ₯Ό κ³ λ €ν•˜λΌ

HEY__ 2024. 6. 8. 15:57
728x90

이 글은 곡뢀λ₯Ό ν•˜λ©΄μ„œ μ•Œκ²Œ 된 λ‚΄μš©λ“€μ„ κΈ°λ‘ν•˜λŠ” κΈ€ μž…λ‹ˆλ‹€. 였λ₯˜λ‚˜ 고쳐야 ν•  사항듀이 μžˆλ‹€λ©΄ 지적 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€!

 

1. μƒμ„±μžμ™€ 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œμ˜ ν•œκ³„

πŸ€” μƒμ„±μžμ™€ 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œκ°€ 가지고 μžˆλŠ” μ œμ•½μœΌλ‘œ 인해 Builder νŒ¨ν„΄μ΄ νƒ„μƒν–ˆλ‹€!

객체λ₯Ό 생성할 λ•Œ, 선택적 λ§€κ°œλ³€μˆ˜κ°€ λ§Žμ€ 경우 적절히 λŒ€μ‘ν•˜κΈ°κ°€ μ–΄λ ΅λ‹€.
예λ₯Ό λ“€μ–΄ `μ˜μ–‘ μ„±λΆ„` ν΄λž˜μŠ€μ— `1회 μ œκ³΅λŸ‰`, `칼둜리`, `총 지방`, `트랜슀 지방`, `포화 지방`, `λ‹¨λ°±μ§ˆ`, `λ‚˜νŠΈλ₯¨` μ΄λΌλŠ” 멀버 λ³€μˆ˜κ°€ μžˆλ‹€κ³  κ°€μ •ν•˜μž.

public class NutritionFacts {
    private final int servingSize;
    private final int calories;
    private final int transFat;
    private final int saturatedFat;
    private final int protein;
    private final int sodium;
}

ν•˜μ§€λ§Œ μ—¬κΈ°μ„œ λ¬Έμ œκ°€ λ°œμƒν•œλ‹€.
각 μ œν’ˆλ§ˆλ‹€ ν¬ν•¨λ˜λŠ” μ˜μ–‘ 성뢄이 λ‹€ λ‹€λ₯Έλ°, μ΄λŸ¬ν•œ 점을 λ°˜μ˜ν•˜λ €λ©΄ μƒμ„±μžλ˜ 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œλ˜ 상관없이 μƒμ„±ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μ—¬λŸ¬ 개 λ§Œλ“€μ–΄μ•Ό ν•œλ‹€λŠ” 것이닀.

이에 λŒ€ν•΄ μš°λ¦¬λŠ” μ„Έ κ°€μ§€μ˜ 해결책을 가지고 μžˆλ‹€.
첫 번째둜 `점측적 μƒμ„±μž νŒ¨ν„΄`, 두 λ²ˆμ§ΈλŠ” `μžλ°” 빈즈 νŒ¨ν„΄`  λ§ˆμ§€λ§‰μœΌλ‘œλŠ” `λΉŒλ” νŒ¨ν„΄`이닀.
각 해결책이 μ–΄λ–€ νŠΉμ§•μ„ 가지고 있고 κ·Έλž˜μ„œ μ™œ λΉŒλ” νŒ¨ν„΄μ„ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ”μ§€ μ•Œμ•„λ³΄μž!

 

 

πŸ’‘ ν•΄κ²°μ±… 1 - 점측적 μƒμ„±μž νŒ¨ν„΄(telescoping constructor pattern)

점측적 μƒμ„±μž νŒ¨ν„΄μ΄λž€ μƒμ„±μžλ₯Ό λ§€κ°œλ³€μˆ˜μ˜ 개수만큼 κ΅¬μ„±ν•˜λŠ” νŒ¨ν„΄μ„ λ§ν•œλ‹€.

public class NutritionFacts {
    private final int servingSize;
    private final int calories;
    private final int transFat;
    private final int protein;
    private final int sodium;

    public NutritionFacts(int servingSize, int calories) {
        this.servingSize = servingSize;
        this.calories = calories;
    }

    public NutritionFacts(int servingSize, int calories, int transFat){
        this.servingSize = servingSize;
        this.calories = calories;
        this.transFat = transFat;
    }

    public NutritionFacts(int servingSize, int calories, int transFat, int protein, int sodium){
        this.servingSize = servingSize;
        this.calories = calories;
        this.transFat = transFat;
        this.protein = protein;
        this.sodium = sodium;
    }
}

μœ„μ˜ μ½”λ“œμ²˜λŸΌ 쑰건에 λ”°λΌμ„œ μƒμ„±μžμ— μ „λ‹¬ν•˜λŠ” 값이 달라진닀면 → 쑰건에 λ§žμΆ°μ„œ μƒμ„±μžλ₯Ό 생성해야 ν•  것이고 → κ·Έ κ²°κ³Ό μƒμ„±μžμ˜ μˆ˜κ°€ λŠ˜μ–΄λ‚  것이닀.

μ΄λŠ” μ‹¬κ°ν•œ 단점듀이 λͺ‡ 개 μžˆλ‹€.

  1. 만일 λ§€κ°œλ³€μˆ˜μ˜ μˆ˜κ°€ 더 λŠ˜μ–΄λ‚œλ‹€λ©΄, 점측적 μƒμ„±μž νŒ¨ν„΄μ„ μ‚¬μš©ν•œ μœ„μ˜ μ½”λ“œλ³΄λ‹€ 가독성이 λ”μš± μ•ˆ μ’‹μ•„μ§ˆ 것이닀.
  2. μ½”λ“œλ₯Ό 읽을 λ•Œ 각 κ°’μ˜ μ˜λ―Έκ°€ 무엇인지도 ν—·κ°ˆλ¦΄ 것이고, λ§€κ°œλ³€μˆ˜κ°€ λ§Žμ•„μ§ˆ λ–„μ—λŠ” λ”μš± λ³΅μž‘ν•΄μ§ˆ 것이닀.
  3. νƒ€μž…μ΄ 같은 λ§€κ°œλ³€μˆ˜κ°€ μ—¬λŸ¬ 개 μžˆλŠ” 경우 μ°ΎκΈ° μ–΄λ €μš΄ λ²„κ·Έλ‘œ μ΄μ–΄μ§ˆ 수 μžˆλ‹€.
  4. ν΄λΌμ΄μ–ΈνŠΈκ°€ μ‹€μˆ˜λ‘œ λ§€κ°œλ³€μˆ˜μ˜ μˆœμ„œμ˜ λ°”κΏ” κ±΄λ„€μ€˜λ„ μ»΄νŒŒμΌλŸ¬λŠ” μ•Œμ•„μ±„μ§€ λͺ»ν•˜κ³  λŸ°νƒ€μž„ μ—λŸ¬λ‘œ 이어진닀.

이λ₯Ό λ³΄μ™„ν•˜κΈ° μœ„ν•΄ λ‚˜νƒ€λ‚œ νŒ¨ν„΄μ΄ `μžλ°” 빈슀 νŒ¨ν„΄(Java bean pattern)`λ‹€.

 

 

πŸ’‘ ν•΄κ²°μ±… 2 - μžλ°” 빈즈 νŒ¨ν„΄(Java bean pattern)

λ§€κ°œλ³€μˆ˜κ°€ μ—†λŠ” μƒμ„±μžλ‘œ 객체λ₯Ό λ§Œλ“  ν›„, setter λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄ λ§€κ°œλ³€μˆ˜μ˜ 값을 μ„€μ •ν•˜λŠ” 방식

public class NutritionFacts {
    private final int servingSize;
    private final int calories;
    private final int transFat;
    private final int protein;
    private final int sodium;

    public NutritionFacts() { }

    public void setServingSize(int val) { servingSize = val; }
    public void setCalories(int val) { calories = val; }
    public void setTransFat(int val) { transFat = val; }
    public void setProtein(int val) { protein = val; }
    public void setSodium(int val) {sodium = val; } 
}
NutritionFacts nutrition = new NutritionFacts();
nutrition.setServingSize(100);
nutrition.setCalories(324);
nutrition.setTransFat(60);
nutrition.setProtein(17);
nutrition.setSodium(100);

ν™•μ‹€νžˆ `점측적 μƒμ„±μž νŒ¨ν„΄`μ—μ„œ λ³Ό 수 μžˆμ—ˆλ˜ 단점듀(수 λ§Žμ€ μƒμ„±μž λ©”μ„œλ“œλ“€)이 보이지 μ•ŠλŠ”λ‹€.
μ½”λ“œκ°€ 쑰금 더 길어지긴 ν–ˆμ§€λ§Œ, μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€κΈ° μ‰¬μš°λ©° 더 읽기 μ‰¬μš΄ μ½”λ“œκ°€ λ˜μ—ˆλ‹€.

ν•˜μ§€λ§Œ `μžλ°” 빈즈 νŒ¨ν„΄`λŠ” μ‹¬κ°ν•œ 단점이 μžˆλ‹€.

  1. 객체가 μ™„μ „νžˆ μƒμ„±λ˜κΈ° μ΄μ „κΉŒμ§€λŠ” 일관성(consistency)κ°€ λ¬΄λ„ˆμ§„ μƒνƒœμ— 놓인닀.
  2. 각 ν”„λ‘œνΌν‹°λ“€μ— λŒ€ν•΄ setter λ©”μ„œλ“œλ“€μ΄ λͺ¨λ‘ μ„ μ–Έλ˜μ–΄μžˆκΈ° λ•Œλ¬Έμ— λΆˆλ³€μœΌλ‘œ λ§Œλ“€ 수 μ—†λ‹€.
  3. setterκ°€ μ—΄λ €μžˆκΈ° λ•Œλ¬Έμ— μ˜λ„μΉ˜ μ•Šκ²Œ ν˜Ήμ€ 고의적으둜 μ™ΈλΆ€μ—μ„œ setterλ₯Ό 톡해 객체의 값을 μˆ˜μ •ν•  수 μžˆλ‹€. μΊ‘μŠν™”/정보은닉을 지킬 수 μ—†κ²Œ λœλ‹€.
  4. μŠ€λ ˆλ“œ μ•ˆμ „μ„±μ„ μ–»κΈ° μœ„ν•΄μ„œλŠ” ν”„λ‘œκ·Έλž˜λ¨Έκ°€ μΆ”κ°€ μž‘μ—…μ„ ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

μ΄λŸ¬ν•œ 단점을 λͺ¨λ‘ λ³΄μ™„ν•˜κ³ , 점측적 μƒμ„±μž νŒ¨ν„΄μ˜ μ•ˆμ „μ„±κ³Ό μžλ°” 빈즈 νŒ¨ν„΄μ˜ κ°€λ…μ„±μ΄λΌλŠ” μž₯점을 가지고 νƒ„μƒν•œ 것이 λ°”λ‘œ `Builder pattern`이닀.

 

 

πŸ’‘ ν•΄κ²°μ±… 3 - λΉŒλ” νŒ¨ν„΄(Builder pattern)

ν•„μš”ν•œ 객체λ₯Ό 직접 λ§Œλ“œλŠ” λŒ€μ‹ , ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§ŒμœΌλ‘œ μƒμ„±μž(or 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œ)λ₯Ό ν˜ΈμΆœν•˜μ—¬ λΉŒλ” 객체λ₯Ό μ–»κ³ ,
setter λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜μ—¬ μ›ν•˜λŠ” 값을 μΆ”κ°€μ μœΌλ‘œ μ„€μ •, λ§ˆμ§€λ§‰μœΌλ‘œ build() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ ν•„μš”ν•œ 객체λ₯Ό μ–»λŠ” νŒ¨ν„΄μ„ λ§ν•œλ‹€.

public class NutritionFacts {
    private final int servingSize;
    private final int calories;
    private final int transFat;
    private final int protein;
    private final int sodium;

    public static class Builder {
        private final int servingSize;
        private final int calories;

        private int transFat;
        private int protein;
        private int sodium;

        public Builder(int servingSize, int calories) {
            this.servingSize = servingSize;
            this.calories = calories;
        }

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

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

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

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

    private NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.calories = builder.calories;
        this.transFat = builder.transFat;
        this.protein = builder.protein;
        this.sodium = builder.sodium;
    }
}

μœ„μ˜ μ½”λ“œλ₯Ό μ‚¬μš©ν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλŠ” λ°‘κ³Ό 같이 μž‘μ„±ν•  수 μžˆλ‹€

NutritionFacts pepsi = new NutritionFacts().Builder(240, 8)
        .transFat(12)
        .protein(3)
        .sodium(35)
        .build();

 

`Build pattern`은 μ—¬λŸ¬κ°€μ§€ μž₯점이 μžˆλ‹€.

  1. μš°μ„  `점측적 μƒμ„±μž νŒ¨ν„΄`μ—μ„œμ˜ 수 λ§Žμ€ μƒμ„±μžμ™€ 그에 νŒŒμƒλ˜λŠ” λ‹€μ–‘ν•œ 단점듀을 보완할 수 μžˆλ‹€.
  2. 거기에 `Java bean pattern`μ—μ„œμ˜ setter λ©”μ„œλ“œλ„ μ‘΄μž¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— `λΆˆλ³€ 객체`둜 λ§Œλ“€ 수 있으며, λ™μ‹œμ— μΊ‘μŠν™”μ™€ 정보 은닉도 지킬 수 μžˆλ‹€.
  3. μƒμ„±μž(정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œ)μ™€λŠ” 달리 멀버 λ³€μˆ˜κ°€ μ„ μ–Έλœ μˆœμ„œλŒ€λ‘œ λ³€μˆ˜λ₯Ό μ„€μ •ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
  4. `Nutrition` ν΄λž˜μŠ€μ—μ„œλŠ” `sodium`이 제일 λ§ˆμ§€λ§‰μ— μ„ μ–Έλœ λ³€μˆ˜μ΄μ§€λ§Œ, `.sodium()`의 μœ„μΉ˜λŠ” 이에 영ν–₯을 받지 μ•ŠλŠ”λ‹€.

Builder 클래슀의 setter λ©”μ„œλ“œλ₯Ό 톡해 값을 μ„€μ •ν•˜κ³ , `Builder 자기 μžμ‹  객체` λ°˜ν™˜ν•¨μœΌλ‘œμ„œ 연쇄적인 호좜이 κ°€λŠ₯ν•˜κ²Œ λ˜μ—ˆλ‹€.
자기 μžμ‹ μ„ λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— 연쇄적인 호좜이 κ°€λŠ₯ν•˜κ³ , μ΄λŸ¬ν•œ 방식을 `Fluent API` ν˜Ήμ€ `Method chaining`이라고 ν•œλ‹€.


2. Builder patternμ—μ„œμ˜ μœ νš¨μ„± 검사

`Builder` 클래슀λ₯Ό 내뢀에 직접 κ΅¬ν˜„ν•˜λŠ” 경우,
1️⃣ Builder 클래슀의 λ©”μ„œλ“œμ— μž…λ ₯된 λ§€κ°œλ³€μˆ˜λ₯Ό κ²€μ‚¬ν•˜κ³  2️⃣ build()μ—μ„œ ν˜ΈμΆœν•˜λŠ” μƒμ„±μžμ—μ„œ ν•œ 번 더 검사할 수 μžˆλ‹€.
각 λ³€μˆ˜λ§ˆλ‹€ μ„€μ •ν•˜λŠ” λ©”μ„œλ“œκ°€ λ”°λ‘œ μžˆμœΌλ―€λ‘œ, 검증을 ν•˜λŠ” 도쀑 λ¬Έμ œκ°€ λ°œμƒν–ˆμ„ λ•Œ μ–΄λ–€ λ§€κ°œλ³€μˆ˜κ°€ 잘λͺ»λ˜μ—ˆλŠ”지 μžμ„Ένžˆ μ•Œλ €μ£ΌκΈ° μš©μ΄ν•˜λ‹€.


3. 계측 ꡬ쑰(상속 관계)μ—μ„œμ˜ Build pattern

`Builder νŒ¨ν„΄`은 κ³„μΈ΅μ μœΌλ‘œ μ„€κ³„λœ ν΄λž˜μŠ€μ™€ ν•¨κ»˜ μ‚¬μš©ν•˜λ©΄ κ·Έ μ‹œλ„ˆμ§€κ°€ λŠ˜μ–΄λ‚œλ‹€.

각 κ³„μΈ΅μ˜ ν΄λž˜μŠ€μ— κ΄€λ ¨ λΉŒλ”λ₯Ό λ©€λ²„λ‘œ μ •ν•œλ‹€. 이 λ•Œ, 좔상 ν΄λž˜μŠ€λŠ” 좔상 λΉŒλ”(abstract builder)λ₯Ό, ꡬ체 ν΄λž˜μŠ€λŠ” ꡬ체 λΉŒλ”(concrete builder)λ₯Ό κ°–κ²Œ ν•œλ‹€.


βœ…Springμ—μ„œμ˜ Builder pattern 적용

이런 Builder pattern에도 단점은 μ‘΄μž¬ν•œλ‹€.
λ°”λ‘œ 각 ν΄λž˜μŠ€λ§ˆλ‹€ `Builder` 클래슀λ₯Ό 내뢀에 생성해주어야 ν•œλ‹€λŠ” 것이닀. 즉, 보일러 ν”Œλ ˆμ΄νŠΈ(Boilerplate) μ½”λ“œκ°€ λ°œμƒν•œλ‹€.

Springμ—μ„œλŠ” `Lombok` 라이브러리λ₯Ό μ‚¬μš©ν•˜μ—¬ 이λ₯Ό μ†μ‰½κ²Œ 생성할 수 μžˆλ‹€.

```java
@Builder
public class NutritionFacts {
private final int servingSize;
private final int calories;
private final int transFat;
private final int protein;
private final int sodium;
}


βœ… μ°Έκ³  자료 & 링크

- Effective Java μ±…

728x90