[JAVA] - 싱글턴

김주형·2022년 11월 30일
1

자라기

목록 보기
20/23
post-thumbnail

Reference


개요

  • 직렬화를 간결하고, 추가 노력없이 사용하기 위해 싱글턴이 사용되듯이
  • 형 변환 코드의 중복을 줄이고, 가독성을 향상시키는 것에 도전했습니다.

숫자 야구 게임

전달받은 문자열을 분리된 정수형 리스트로 변환해줘야 하는 상황

  • 문자열을 char로 변환 후 리스트에 추가
  • 변경된 리스트를 정수형으로 변환

개선 전

public class Player {

     public List<Integer> generateNumber(String readLine) {

         return (List<Integer>) separateReadLine(readLine)
                 .stream()
                 .map(Character::getNumericValue)
                 .collect(toList());
     }

     private static List<Character> separateReadLine(String readLine) {
         List<Character> separate = new ArrayList<>();
         for (int index = 0; index < LENGTH.getNumber(); index++) {
             char eachNumber = readLine.charAt(index);
             separate.add(eachNumber);
         }
         return separate;
     }

 }
  • 테스트 (green)
     @ParameterizedTest
     @MethodSource("parametersProvider")
     public void 사용자_랜덤수_생성(String readLine, List<Integer> expect){
         //given
         System.setIn(setReadLine(readLine));

         //when
         List<Integer> actual = player.generateNumber(inputView.putReadLine());

         //then
         assertEquals(expect, actual);
     }

     static Stream<Arguments> parametersProvider() {
         return Stream.of(
                 arguments("123", List.of(1,2,3)),
                 arguments("456", List.of(4,5,6)),
                 arguments("789", List.of(7,8,9))
         );
     }

문제점

  • 객체가 객체스럽지 않다.
  • 테스트 코드에서 기차 충돌 발생
  • 이후 프로덕션 코드 구축에서 불필요한 인스턴스 생성으로 디버깅하기 불편했음

고민한 점

  • 불필요한 인스턴스 생성을 방지해야 한다.
    • 싱글턴을 보장받는 방법 시도
    • 학습 기록을 참고하여 열거형 선택
  • 읽는 입장에서 추적하기 편하도록 생성 위치를 관리해야 한다.
    • 데이터를 꺼내지 않고 메시지를 던질 수 있도록 개선

개선 후

public enum Player {
    PLAYER;

    private String readLine;


    public List<Integer> number() {
        this.readLine = new InputView().putReadLine();
        return generateNumber(readLine);
    }

    private List<Integer> generateNumber(String readLine) {

        return separate().stream()
                .map(Character::getNumericValue)
                .collect(Collectors.toList());
    }

    private List<Character> separate() {
        List<Character> player = new ArrayList<>();
        readLine.chars()
                .mapToObj(element -> (char) element)
                .forEach(player::add);
        return player;
    }
    
}
  • 테스트는 refactor -> red 였습니다.

고민2

  • 가독성은 미미하게 향상된게 맞는 것 같다.
  • 객체가 메시지를 던지는 것은 반쯤 맞은 것 같다.
  • 역직렬화를 예방할 수 있는가?
  • 복잡한 공격(직렬화, 리플렉션)을 막을 수 있는가?

Re-refactor

  • '문자열을 char로 변환' 분리
public enum Separator {
    SEPARATOR;

    private List<Character> separator;
    
    public List<Character> execute() {
        String readLine = new InputView().putReadLine();
        separator = new ArrayList<>();
        return separate(readLine);
    }

    private List<Character> separate(String readLine) {

        readLine.chars()
                .mapToObj(element -> (char) element)
                .forEach(separator::add);
        return separator;
    }
}
public enum Player {
    PLAYER;

    private String readLine;

    public List<Integer> number() {

        return generateNumber();
    }


    private List<Integer> generateNumber() {
        return SEPARATOR.execute()
                .stream()
                .map(Character::getNumericValue)
                .collect(Collectors.toList());
    }

}
  • Player가 싱글턴임이 보장
  • 형 변환의 추가 노력 제거
    • 가비지 컬렉터에게 맡길 필요 제거
  • 간결함 미미한 증가
    @ParameterizedTest
    @MethodSource("parametersProvider")
    public void 사용자_랜덤수_생성(String readLine, List<Integer> expect){
        //given
        System.setIn(setReadLine(readLine));

        //when
        List<Integer> actual = PLAYER.number();

        //then
        assertEquals(expect, actual);
    }

    static Stream<Arguments> parametersProvider() {
        return Stream.of(
                arguments("123", List.of(1,2,3)),
                arguments("456", List.of(4,5,6)),
                arguments("789", List.of(7,8,9))
        );
    }
}
  • 테스트는 green
profile
도광양회

0개의 댓글