📜 제목으로 보기

싱글톤과 캐싱

싱글톤을 이용한 [단일객체 CACHE] 적용과 TDD

싱글톤을 이용한 CACHE 생성 2가지 방법(정리용)
  1. static final으로 클래스 CACHE변수; 선언

    • 바로 옆에서 = 할당하며 초기화 or 길어서 static { 클래스변수 = } 블럭(final도 가능)에서 초기화
      • 바로 초기화하든, static블럭에서 초기화하든 null이 아닌 상태의 캐쉬변수
    • 이후 정팩메로만 생성된 CACHE객체 or 생성된 CACHE자료구조에서 1개만 get하여 제공
      • 이후 기본생성자는 CACHE 최초 초기화하며 채울때만 사용됨
     private static final Map<Integer, LottoNumber> LOTTO_NUMBER_CACHE;
        
     static {
         LOTTO_NUMBER_CACHE = IntStream.rangeClosed(LOTTO_NUMBER_MIN, LOTTO_NUMBER_MAX)
             .boxed()
             .collect(Collectors.toMap(number -> number, LottoNumber::new));
     }
        
     private final int number;
        
     public LottoNumber(final int inputNumber) {
         this.number = inputNumber;
     }
        
     public static LottoNumber from(final String inputNumber) {
         final int number = Integer.parseInt(inputNumber);
         validateLottoNumberRange(number);
         return LOTTO_NUMBER_CACHE.get(number);
     }
    
  2. static으로 클래스 CACHE변수; 선언 (no final)

    • 이후 정팩메에 synchronized달아주고

    • 정펙매내부에서 초기화 한번도 안된 최초 if (CACHE == null) 상태의 캐쉬변수일 때만, 기본생성자로 CACHE변수 초기화
    • 정펙매로만 생성된 CACHE객체 or 생성된 CACHE자료구조에서 1개만 get하여 제공
     private static Map<Integer, LottoNumber> LOTTO_NUMBER_CACHE;
     private final int number;
        
     private LottoNumber(final int number) {
         checkNumberRightRange(number);
         this.number = number;
     }
        
     public synchronized static LottoNumber valueOf(final int number) {
         checkNumberRightRange(number);
         if (LOTTO_NUMBER_CACHE == null) {
             LOTTO_NUMBER_CACHE = IntStream.rangeClosed(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
                 .boxed()
                 .collect(Collectors.toMap(lottoNumber -> lottoNumber, LottoNumber::new));
         }
         return LOTTO_NUMBER_CACHE.get(number);
     }
    
Test에서부터 빨간줄로 넘어가 -> 캐쉬 적용하기
  1. 캐쉬로 받아먹으려면 싱글톤 받아주는 정펙매(getInstance())로만 가져와야한다.

    • 네이밍을 그렇게 했어도 기본 생성자에 넣어줄 파라미터도 받아야한다. image-20220308230247397
  2. 싱글톤의 동시성 잇슈를 처리하기 위해 synchronized 접근제한자 <-> static 사이에 넣어준다. image-20220308230318968

     public synchronized static void getInstance(final int value) {
        
     }
    
  3. 앞으로 정펙매로만 제공되어야하므로 도메인 검증을 여기로 옮기자.

    • 기존 도메인테스트가 new 기본생성자()로 진행되어있으니, 그냥 두고 ->
      • 맨 나중에 도메인검증을 정팩메코드로 + 기본생성자는 private + 검증은 삭제하도록 수정하자..
     public LottoNumber(final int value) {
         // 지금은 임시로 살려둔다. 테스트를 나중에 바꿀 때 private로 변경 + 검증삭제
         validate(value);
         this.value = value;
     }
        
     public synchronized static void getInstance(final int value) {
         //2. 검증은 여기로 옮긴다. 왜냐면, 앞으로 CACHE정팩메로만 제공될 것이기 때문.
         //   - 정적안에 들어가므로 검증메서드가 static으로 바뀐다. 
         validate(value);
     }
    
  4. CACHEstatic변수 선언을 직접 안하고 빨간줄생성 하기 위해,

    • 빨간줄로 if ( == null )부터 static 초기화 를 작성해줬다.
      • 기본생성자는 이제 여기 캐쉬생성에서만 이용되어야한다.
        • 원래는 private으로 바꾸고, TEST다 처내야함.
    • static 메서드 내 자동 생성시 constant = static final이므로 그냥 field -> static필드로 생성하자.

    image-20220308232128686 image-20220308232148140 image-20220308232253457 image-20220308232419786

     public class LottoNumber {
        
         private static final int MIN = 1;
         private static final int MAX = 45;
         // 자동 생성한 CACHE field (static)
         private static Map<Integer, LottoNumber> CACHE;
        
         private final int value;
        
         public synchronized static void getInstance(final int value) {
             validate(value);
             //3. 캐쉬로 제공될 static 변수;  만 선언하고 -> if null일 때 = 최초에만 초기화 시켜준다.
             // - Map으로 만들거면
             // - 중간에 .map( number -> )없이 맨 나중에 toMap으로 만들면 된다.
             // - intStream은 Integer가 아니다. toMap에 들어가려면 .boxed() 먼저 해주고 들어가야한다.
             // - toMap의 2번쨰 인자는 method reference만 사용 가능하다. new LottoNumber(Number하니 안되더라)
             if (CACHE == null) {
                 CACHE = IntStream.rangeClosed(MIN, MAX)
                     //.map( number -> )
                     .boxed()
                     .collect(Collectors.toMap(number -> number, LottoNumber::new));
             }
         }
    
  5. 정팩메에서 캐쉬객체 or 캐쉬 자료구조.get()한 것을 응답해준다.

     public synchronized static LottoNumber getInstance(final int value) {
         validate(value);
         if (CACHE == null) {
             CACHE = IntStream.rangeClosed(MIN, MAX)
                 .boxed()
                 .collect(Collectors.toMap(number -> number, LottoNumber::new));
         }
        
         // 제공은 이제 기본생성자가 아닌, CACHE에서 제공해준다.
         return CACHE.get(value);
     }
    
검증이 넘어간 캐쉬 정팩메로 생성 테스트로 마무리
@Test
void cache_create() {
    assertDoesNotThrow(() -> LottoNumber.getInstance(6));
}