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

[Effective Java] item 03. private μƒμ„±μžλ‚˜ μ—΄κ±° νƒ€μž…μœΌλ‘œ μ‹±κΈ€ν„΄μž„μ„ λ³΄μ¦ν•˜λΌ

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

Item 3. private μƒμ„±μžλ‚˜ μ—΄κ±° νƒ€μž…μœΌλ‘œ μ‹±κΈ€ν„΄μž„μ„ λ³΄μ¦ν•˜λΌ

1. μ‹±κΈ€ν„΄(Singleton)μ΄λž€?

μ‹±κΈ€ν„΄(singleton)μ΄λž€ μΈμŠ€ν„΄μŠ€λ₯Ό 였직 ν•˜λ‚˜λ§Œ 생성할 수 μžˆλŠ” 클래슀λ₯Ό λ§ν•œλ‹€.

 

μ‹±κΈ€ν„΄μ˜ μ „ν˜•μ μΈ μ˜ˆλ‘œλŠ” 섀계 상 μœ μΌν•΄μ•Ό ν•˜λŠ” μ‹œμŠ€ν…œ μ»΄ν¬λ„ŒνŠΈλ‚˜ λ¬΄μƒνƒœ(stateless) 객체(Spring bean component)κ°€ μžˆλ‹€.
μ‹±κΈ€ν„΄ 객체에도 단점이 μžˆλŠ”λ°, λ°”λ‘œ μ‹±κΈ€ν„΄ 객체λ₯Ό μ΄μš©ν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈλ₯Ό ν…ŒμŠ€νŠΈν•˜κΈ° μ–΄λ ΅λ‹€λΌλŠ” 것이닀.

 

만일 ν•΄λ‹Ή νƒ€μž…μ„ `interface`둜 μ •μ˜ν•œ λ‹€μŒ κ·Έ `interface `κ΅¬ν˜„ν•΄μ„œ λ§Œλ“  싱글턴인 경우,
μ‹±κΈ€ν„΄ μΈμŠ€ν„΄μŠ€λ₯Ό `mock(κ°€μ§œ 객체)`으둜 κ΅¬ν˜„ν•˜μ—¬ λŒ€μ²΄ν•  수 μžˆμ§€λ§Œ, 그렇지 μ•Šμ€ κ²½μš°μ—λŠ” ν…ŒμŠ€νŠΈ ν•˜λŠ”λ°μ— 어렀움을 κ²ͺ을 수 μžˆλ‹€.

 


2. 싱글턴을 λ§Œλ“œλŠ” μ„Έ 가지 방법

싱글턴을 λ§Œλ“œλŠ” 두 가지 방법 λͺ¨λ‘, μƒμ„±μžλŠ” private둜 감좰주고, public static멀버λ₯Ό 톡해 μ ‘κ·Όν•˜λŠ” 방식을 μ‚¬μš©ν•œλ‹€.

 

1️⃣ `public static` 멀버가 `final` ν•„λ“œμΈ 방식

public class Bean {
    public static final Bean INSTANCE = new Bean();

    private Bean() { }

    ...
}

publicμ΄λ‚˜ protected μƒμ„±μžκ°€ μ—†κ³  private μƒμ„±μžλ§Œ μ‘΄μž¬ν•˜λ©°, private μƒμ„±μžλŠ” public static final ν•„λ“œμΈ Bean.INSTANCEλ₯Ό μ΄ˆκΈ°ν™”ν•  λ•Œ λ”± ν•œ 번만 ν˜ΈμΆœλœλ‹€.

λ”°λΌμ„œ Bean ν΄λž˜μŠ€κ°€ μ΄ˆκΈ°ν™”λ˜λ©° λ§Œλ“€μ–΄μ§„ μΈμŠ€ν„΄μŠ€κ°€ 단 ν•˜λ‚˜λΏμž„μ„ 보μž₯ν•  수 μžˆλ‹€.

μ΄λŸ¬ν•œ λ°©μ‹μ˜ μž₯점은 λ°‘κ³Ό κ°™λ‹€.

  1. ν•΄λ‹Ή ν΄λž˜μŠ€κ°€ μ‹±κΈ€ν„΄(singleton)μž„μ΄ API에 λͺ…λ°±νžˆ λ“œλŸ¬λ‚œλ‹€λŠ” 것
    public static ν•„λ“œκ°€ finalμ΄λ―€λ‘œ μ ˆλŒ€λ‘œ λ‹€λ₯Έ 객체λ₯Ό μ°Έμ‘°ν•  수 μ—†λ‹€.
  2. λ³„λ„μ˜ λ©”μ„œλ“œλ₯Ό λ”°λ‘œ μž‘μ„±ν•˜μ§€ μ•Šμ•„λ„ 되기 λ•Œλ¬Έμ— κ°„κ²°ν•˜λ‹€

⚠️ ν•˜μ§€λ§Œ μ˜ˆμ™Έλ„ ν•˜λ‚˜ μ‘΄μž¬ν•˜λŠ”λ°, κΆŒν•œμ΄ μžˆλŠ” ν΄λΌμ΄μ–ΈνŠΈκ°€ Reflection API인 AccssibleObject.setAccessible()을 μ‚¬μš©ν•΄ private μƒμ„±μžλ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€.
μ΄λŸ¬ν•œ μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λ €λ©΄, μƒμ„±μžλ₯Ό μˆ˜μ •ν•˜μ—¬ 두 번째 객체가 μƒμ„±λ˜λ €ν•  λ•Œ μ˜ˆμ™Έλ₯Ό λ˜μ§€κ²Œ ν•˜λ©΄ λœλ‹€.

 

 

2️⃣ 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œλ₯Ό public static λ©€λ²„λ‘œ μ œκ³΅ν•˜λŠ” 방식

public class Bean {
    private static final Bean INSTANCE = new Bean();

    private Bean() { }

    public static Bean getInstance() {
        return INSTANCE;
    }
    ...
}

μš°λ¦¬λŠ” Item 1μ—μ„œ 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œμ— λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜λ‹€.

'정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œλ₯Ό μ°ΎκΈ° νž˜λ“€λ‹€'λΌλŠ” 단점을 λ³΄μ™„ν•˜κΈ° μœ„ν•΄ 주둜 μ‚¬μš©ν•˜λŠ” λͺ…λͺ… 방식을 μ‚¬μš©ν•œλ‹€.

κ·Έ 쀑, `getInstance()`λŠ” μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•  λ•Œ μ‚¬μš©ν•˜λŠ” λͺ…λͺ…법인데, ν•΄λ‹Ή λ©”μ„œλ“œλŠ” 항상 같은 객체의 μ°Έμ‘°λ₯Ό λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ—
μΈμŠ€ν„΄μŠ€κ°€ 단 ν•˜λ‚˜ λΏμž„μ„ 보μž₯ν•  수 μžˆλ‹€.

단, 첫번째 방법과 같이 Reflection을 μ‚¬μš©ν•˜μ—¬ private μƒμ„±μžλ₯Ό ν˜ΈμΆœν•  수 μžˆλŠ” 것은 λ§ˆμ°¬κ°€μ§€μ΄λ―€λ‘œ, 좔가적인 μ˜ˆμ™Έ μ²˜λ¦¬κ°€ ν•„μš”ν•˜λ‹€.

μ΄λŸ¬ν•œ λ°©μ‹μ˜ μž₯점은 λ‹€μŒκ³Ό κ°™λ‹€.

 

1. 만일, ν•΄λ‹Ή 클래슀λ₯Ό 더이상 μ‹±κΈ€ν„΄μœΌλ‘œ μœ μ§€ν•˜κ³  싢지 μ•Šλ‹€λ©΄, `APIλ₯Ό 바꾸지 μ•Šκ³ λ„ λ³€κ²½`ν•  수 μžˆλ‹€.

public class Bean { 
	private Bean instance; 
	private Bean() { } 
    
	// 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œλ§Œ 적용 
	public static Bean from() { return new Bean(); } 
}

 

2. 정적 νŒ©ν„°λ¦¬λ₯Ό `μ œλ„ˆλ¦­ μ‹±κΈ€ν„΄ νŒ©ν„°λ¦¬(Generic Singleton factory); item 30`둜 λ³€κ²½ν•  수 μžˆλ‹€λŠ” 것이닀.

public class Bean { 
    private static final Bean INSTANCE = new Bean(); 
    
    private Bean() { } 
    
    public static Bean getInstance() { return (Bean) INSTANCE; } 
    public void say(T t) { System.out.println(t); } 
}

 

 

3. 정적 νŒ©ν„°λ¦¬μ˜ λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό `Supplier`둜 μ‚¬μš©ν•  수 μžˆλ‹€λŠ” 점이닀.

public class Bean { 
    private static final Bean INSTANCE = new Bean(); 
    private Bean() { } 
    public static Bean getInstance() { return INSTANCE; } 
}

public static void main(String[] args) { 
	Supplier supplier = Bean::getInstance; 
    Bean bean = supplier.get(); 
    // μ΄ν›„μ—λŠ” bean의 λ©”μ„œλ“œ 호좜 
}

 

만일 μœ„μ˜ μž₯점을 ν™œμš©ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€λ©΄, 첫번째 방법(public ν•„λ“œ 방식)이 더 μ’‹λ‹€.

 

 

3️⃣ μ—΄κ±° νƒ€μž…(Enum)을 μ‚¬μš©ν•˜λŠ” 방법

public enum Bean {
    INSTANCE;

    //밑에 λ©”μ„œλ“œ μΆ”κ°€ κ΅¬ν˜„
}

1번 방식(public static final ν•„λ“œ)κ³Ό λΉ„μŠ·ν•˜μ§€λ§Œ, 더 κ°„κ²°ν•˜κ³  좔가적인 κ΅¬ν˜„μ—†μ΄ 직렬화(Serialize)도 ν•  수 μžˆλ‹€.
심지어 μ•„μ£Ό λ³΅μž‘ν•œ 직렬화 μƒν™©μ΄λ‚˜, Reflection 곡격에도 제 2의 μΈμŠ€ν„΄μŠ€κ°€ μƒκΈ°λŠ” 일을 μ™„λ²½ν•˜κ²Œ 막아쀀닀.

λΆ€μžμ—°μŠ€λŸ¬μ›Œ 보일 수 μžˆμœΌλ‚˜, λŒ€λΆ€λΆ„μ˜ μƒν™©μ—μ„œλŠ” μ›μ†Œκ°€ ν•˜λ‚˜ 뿐인 μ—΄κ±° νƒ€μž…μ΄ 싱글턴을 λ§Œλ“œλŠ” κ°€μž₯ 쒋은 방법이닀.

ν•˜μ§€λ§Œ 이 방법에도 ν•œ 가지 단점이 μžˆλ‹€.
싱글턴이 Enum 외에 Concrete classλ₯Ό 상속(extends)을 ν•  수 μ—†κΈ° λ•Œλ¬Έμ—, 만일 νŠΉμ • 클래슀λ₯Ό 상속해야 ν•œλ‹€λ©΄ 이 λ°©λ²•μ€μ‚¬μš©ν•  수 μ—†λ‹€.

 


3. μ‹±κΈ€ν„΄ 클래슀λ₯Ό 직렬화(Serialize)ν•˜λŠ” 방법

1번 방법(public static final ν•„λ“œ)λ‚˜ 2번 방법(정적 νŒ©ν„°λ¦¬ 적용)으둜 λ§Œλ“  μ‹±κΈ€ν„΄ 클래슀λ₯Ό μ§λ ¬ν™”ν•˜κΈ° μœ„ν•΄μ„œλŠ”
Serialize interfaceλ₯Ό κ΅¬ν˜„ν•œλ‹€κ³  μ„ μ–Έν•˜λŠ” κ²ƒλ§ŒμœΌλ‘œλŠ” λΆ€μ‘±ν•˜λ‹€.

λͺ¨λ“  μΈμŠ€ν„΄μŠ€ ν•„λ“œλ₯Ό μΌμ‹œμ (transient)라고 μ„ μ–Έν•˜κ³ , readResolve λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•΄μ•Ό ν•œλ‹€.
만일 μ œκ³΅ν•˜μ§€ μ•Šμ€ κ²½μš°μ—λŠ” μ§λ ¬ν™”ν•œ μΈμŠ€ν„΄μŠ€λ₯Ό 역직렬화할 λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€κ°€ λ§Œλ“€μ–΄μ§„λ‹€.

public class Bean implements Serializable{
    private static final Bean INSTANCE = new Bean();

    private Bean() { }

    public static Bean getInstance() {
        return INSTANCE;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}
728x90