싱글톤으로 캐싱(로또번호)
싱글톤 작성방법 2가지 및 단일객체 캐싱
📜 제목으로 보기
싱글톤과 캐싱
싱글톤을 이용한 [단일객체 CACHE] 적용과 TDD
싱글톤을 이용한 CACHE 생성 2가지 방법(정리용)
-
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); }
- 바로 옆에서
-
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에서부터 빨간줄로 넘어가 -> 캐쉬 적용하기
-
캐쉬로 받아먹으려면
싱글톤 받아주는 정펙매(getInstance())
로만 가져와야한다.- 네이밍을 그렇게 했어도
기본 생성자에 넣어줄 파라미터
도 받아야한다.
- 네이밍을 그렇게 했어도
-
싱글톤의 동시성 잇슈를 처리하기 위해
synchronized
를 접근제한자 <-> static 사이에 넣어준다.public synchronized static void getInstance(final int value) { }
-
앞으로
정펙매
로만 제공되어야하므로도메인 검증
을 여기로 옮기자.- 기존 도메인테스트가
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); }
- 기존 도메인테스트가
-
CACHE
의 static변수 선언을 직접 안하고 빨간줄생성 하기 위해,- 빨간줄로
if ( == null )
부터static 초기화
를 작성해줬다.-
기본생성자
는 이제 여기 캐쉬생성에서만 이용되어야한다.- 원래는 private으로 바꾸고, TEST다 처내야함.
-
- static 메서드 내 자동 생성시 constant = static final이므로 그냥
field -> static
필드로 생성하자.
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)); } }
- 빨간줄로
-
정팩메
에서캐쉬객체
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));
}