Singleton Design Pattern - DanielWorld/SoftwareTech GitHub Wiki

Singleton Design Pattern μ΄λž€?

ν•˜λ‚˜μ˜ ν΄λž˜μŠ€μ— ν•˜λ‚˜μ˜ 객체만 생성 ν•˜λ„λ‘ ν•˜λ©°, μƒμ„±λœ 객체λ₯Ό μ–΄λ””μ—μ„œλ“ μ§€ μ°Έμ‘°ν•  수 μžˆλ„λ‘ ν•˜λŠ” νŒ¨ν„΄ ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€λ§Œμ„ μƒμ„±ν•˜λŠ” μ±…μž„μ΄ 있으며 getInstance μ „μ—­ λ©”μ„œλ“œλ₯Ό 톡해 λͺ¨λ“  ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ λ™μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•œλ‹€.

Singleton Pros and Cons

  1. μž₯점 :
  • κ³ μ •λœ λ©”λͺ¨λ¦¬ μ˜μ—­μ„ μ–»μœΌλ©΄μ„œ ν•œλ²ˆμ˜ new둜 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— λ©”λͺ¨λ¦¬ λ‚­λΉ„λ₯Ό λ°©μ§€ν•  수 있음
  • 객체의 생성과 쑰합을 μΊ‘μŠν™”ν•΄ νŠΉμ • 객체가 μƒμ„±λ˜κ±°λ‚˜ λ³€κ²½λ˜μ–΄λ„ ν”„λ‘œκ·Έλž¨ ꡬ쑰에 영ν–₯을 크게 λ°›μ§€ μ•Šλ„λ‘ μœ μ—°μ„±μ„ μ œκ³΅ν•œλ‹€.
  • 맀번 νŠΉμ • 객체λ₯Ό 생성할 ν•„μš” 없이 ν•˜λ‚˜λ§Œ μƒμ„±λœ 객체λ₯Ό μ–΄λ””μ—μ„œλ“ μ§€ μ°Έμ‘°ν•  수 μžˆλ‹€. μ‹€μƒν™œμ— μ μš©ν•˜μžλ©΄ ν”„λ¦°ν„° ν•˜λ‚˜λ₯Ό μ—¬λŸ¬λͺ…μ΄μ„œ μ‚¬μš©ν•  경우λ₯Ό 예둜 λ“€ 수 μžˆλ‹€. μ•ˆλ“œλ‘œμ΄λ“œ μ•± 같은 경우 각 μ•‘ν‹°λΉ„ν‹°λ‚˜ ν΄λž˜μŠ€λ³„λ‘œ μ£Όμš” ν΄λž˜μŠ€λ“€μ„ 일일이 μ „λ‹¬ν•˜κΈ°κ°€ 번거둭기 λ•Œλ¬Έμ— 싱글톀 클래슀λ₯Ό λ§Œλ“€μ–΄ μ–΄λ””μ„œλ‚˜ μ ‘κ·Όν•˜λ„λ‘ μ„€κ³„ν•˜λŠ” 것이 νŽΈν•¨
  1. 단점 :
  • 싱글톀 μΈμŠ€ν„΄μŠ€κ°€ λ„ˆλ¬΄ λ§Žμ€ 일을 ν•˜κ±°λ‚˜ λ§Žμ€ 데이터λ₯Ό κ³΅μœ μ‹œν‚¬ 경우 λ‹€λ₯Έ 클래슀의 μΈμŠ€ν„΄μŠ€λ“€ 간에 결합도가 λ†’μ•„μ Έ 개방-폐쇄 원칙 을 μœ„λ°°ν•˜κ²Œ λœλ‹€. (객체 μ§€ν–₯ 섀계 원칙에 어긋남) λ”°λΌμ„œ μˆ˜μ •μ΄ μ–΄λ €μ›Œμ§€κ³  ν…ŒμŠ€νŠΈν•˜κΈ° μ–΄λ €μ›Œμ§„λ‹€.

  • multithreading ν™˜κ²½μ—μ„œ Singleton νŒ¨ν„΄μ„ μ‚¬μš©ν•œ ν΄λž˜μŠ€μ— μ ‘κ·Όν•  λ•Œ, instance κ°€ 1개 이상 μƒμ„±λ˜λŠ” κ²½μš°κ°€ λ°œμƒ (κ²½ν•© 쑰건) -> Lazy initialization (λŠ¦μ€ μ΄ˆκΈ°ν™” 방식)을 μ‚¬μš©ν•  경우 λ¬Έμ œκ°€ λ°œμƒ

κ²°λ‘  : κΌ­ ν•„μš”ν•œ κ²½μš°μ•„λ‹ˆλ©΄ 지양해야함. (적절히 잘 μ“°λ©΄ μ•„μ£Ό μ’‹μŒ)

How to avoid race condition of Singleton in Multithreading

  • 정적 λ³€μˆ˜μ— μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ λ°”λ‘œ μ΄ˆκΈ°ν™”ν•˜λŠ” 방법 (Eager Initialization)
public class Printer {
   // static λ³€μˆ˜μ— 외뢀에 μ œκ³΅ν•  자기 μžμ‹ μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ μ΄ˆκΈ°ν™”
   private static Printer printer = new Printer();
   private Printer() { }
   // 자기 μžμ‹ μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό 외뢀에 제곡
   public static Printer getPrinter(){
     return printer;
   }
   public void print(String str) {
     System.out.println(str);
   }
}

static λ³€μˆ˜ : 객체가 μƒμ„±λ˜κΈ° μ „ ν΄λž˜μŠ€κ°€ λ©”λͺ¨λ¦¬μ— λ‘œλ”©λ  λ•Œ λ§Œλ“€μ–΄μ Έ μ΄ˆκΈ°ν™”κ°€ ν•œ 번만 μ‹€ν–‰λœλ‹€. : ν”„λ‘œκ·Έλž¨ μ‹œμž‘~μ’…λ£ŒκΉŒμ§€ μ—†μ–΄μ§€μ§€ μ•Šκ³  λ©”λͺ¨λ¦¬μ— 계속 μƒμ£Όν•˜λ©° ν΄λž˜μŠ€μ—μ„œ μƒμ„±λœ λͺ¨λ“  κ°μ²΄μ—μ„œ μ°Έμ‘°ν•  수 μžˆλ‹€.

μž₯점 : static으둜 μƒμ„±λœ λ³€μˆ˜μ— 싱글톀 객체λ₯Ό μ„ μ–Έν–ˆκΈ° λ•Œλ¬Έμ— 클래슀 λ‘œλ”μ— μ˜ν•΄ ν΄λž˜μŠ€κ°€ λ‘œλ”© 될 λ•Œ 싱글톀 객체가 μƒμ„±λ©λ‹ˆλ‹€. 또 클래슀 λ‘œλ”μ— μ˜ν•΄ ν΄λž˜μŠ€κ°€ 졜초 λ‘œλ”© 될 λ•Œ 객체가 μƒμ„±λ¨μœΌλ‘œ Thread-safeν•©λ‹ˆλ‹€.

단점 : 싱글톀객체 μ‚¬μš©μœ λ¬΄μ™€ 관계없이 ν΄λž˜μŠ€κ°€ λ‘œλ”©λ˜λŠ” μ‹œμ μ— 항상 싱글톀 객체가 μƒμ„±λ˜κ³ , λ©”λͺ¨λ¦¬λ₯Ό 작고있기 λ•Œλ¬Έμ— λΉ„νš¨μœ¨μ μΌ 수 μžˆλ‹€.

  • μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“œλŠ” λ©”μ„œλ“œμ— λ™κΈ°ν™”ν•˜λŠ” 방법 (Thread-Safe Initialization)
public class Printer {
   // 외뢀에 μ œκ³΅ν•  자기 μžμ‹ μ˜ μΈμŠ€ν„΄μŠ€
   private static Printer printer = null;
   private int counter = 0;
   private Printer() { }
   // μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“œλŠ” λ©”μ„œλ“œ 동기화 (μž„κ³„ ꡬ역)
   public synchronized static Printer getPrinter(){
     if (printer == null) {
       printer = new Printer(); // Printer μΈμŠ€ν„΄μŠ€ 생성
     }
     return printer;
   }
   public void print(String str) {
     // 였직 ν•˜λ‚˜μ˜ μŠ€λ ˆλ“œλ§Œ 접근을 ν—ˆμš©ν•¨ (μž„κ³„ ꡬ역)
     // μ„±λŠ₯을 μœ„ν•΄ ν•„μš”ν•œ λΆ€λΆ„λ§Œμ„ μž„κ³„ κ΅¬μ—­μœΌλ‘œ μ„€μ •ν•œλ‹€.
     synchronized(this) {
       counter++;
       System.out.println(str + counter);
     }
   }
}

μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“œλŠ” λ©”μ„œλ“œλ₯Ό μž„κ³„ κ΅¬μ—­μœΌλ‘œ λ³€κ²½

  1. 닀쀑 μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ λ™μ‹œμ— μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ getPrinter λ©”μ„œλ“œλ₯Ό μ†Œμœ ν•˜λŠ” 객체에 μ ‘κ·Όν•˜λŠ” 것을 λ°©μ§€ν•œλ‹€. 곡유 λ³€μˆ˜μ— μ ‘κ·Όν•˜λŠ” 뢀뢄을 μž„κ³„ κ΅¬μ—­μœΌλ‘œ λ³€κ²½

  2. μ—¬λŸ¬ 개의 μŠ€λ ˆλ“œκ°€ ν•˜λ‚˜λΏμΈ counter λ³€μˆ˜ 값에 λ™μ‹œμ— μ ‘κ·Όν•΄ κ°±μ‹ ν•˜λŠ” 것을 λ°©μ§€ν•œλ‹€. getInstance()에 Lock을 ν•˜λŠ” 방식이라 속도가 λŠλ¦¬λ‹€.

κ²°λ‘  :

  • Initialization on demand holder idiom (holder에 μ˜ν•œ μ΄ˆκΈ°ν™”)

이 방법은 ν΄λž˜μŠ€μ•ˆμ— 클래슀(Holder)λ₯Ό 두어 JVM의 Class Loader λ§€μ»€λ‹ˆμ¦˜κ³Ό Classκ°€ λ‘œλ“œλ˜λŠ” μ‹œμ μ„ μ΄μš©ν•œ λ°©λ²•μž…λ‹ˆλ‹€. Lazy initialization 방식을 κ°€μ Έκ°€λ©΄μ„œ Threadκ°„ λ™κΈ°ν™”λ¬Έμ œλ₯Ό λ™μ‹œμ— ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ€‘μ²©ν΄λž˜μŠ€ HolderλŠ” getInstance λ©”μ„œλ“œκ°€ 호좜되기 μ „μ—λŠ” μ°Έμ‘° λ˜μ§€ μ•ŠμœΌλ©°, 졜초둜 getInstance() λ©”μ„œλ“œκ°€ 호좜 될 λ•Œ 클래슀 λ‘œλ”μ— μ˜ν•΄ 싱글톀 객체λ₯Ό μƒμ„±ν•˜μ—¬ λ¦¬ν„΄ν•©λ‹ˆλ‹€. μš°λ¦¬κ°€ μ•Œμ•„λ‘¬μ•Ό ν•  것은 holder μ•ˆμ— μ„ μ–Έλœ instanceκ°€ static이기 λ•Œλ¬Έμ— 클래슀 λ‘œλ”© μ‹œμ μ— ν•œλ²ˆλ§Œ ν˜ΈμΆœλœλ‹€λŠ” 점을 μ΄μš©ν•œκ²ƒμ΄μ£ . 또 final을 μ¨μ„œ λ‹€μ‹œ 값이 ν• λ‹Ήλ˜μ§€ μ•Šλ„λ‘ ν•©λ‹ˆλ‹€.

public class InitializationOnDemandHolderIdiom {
 
    private InitializationOnDemandHolderIdiom(){}
     
    private static class SingleTonHolder{
        private static final InitializationOnDemandHolderIdiom instance = new InitializationOnDemandHolderIdiom();
    }
     
    public static InitializationOnDemandHolderIdiom getInstance(){
        return SingleTonHolder.instance;
    }
}