
벌써 3주차 프리코스가 시작되었다 이번 3주차의 요구사항은 아래와 같다.
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- 로또 1장의 가격은 1,000원이다.
- 당첨 번호와 보너스 번호를 입력받는다.
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우
IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
Exception이 아닌IllegalArgumentException,IllegalStateException등과 같은 명확한 유형을 처리한다.
- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- else 예약어를 쓰지 않는다.
- Java Enum을 적용한다.
위 요구사항들을 구현하기 위해 작성한 기능목록들은 아래와 같다.
InputManager
- 사용자가 입력한 로또 금액 반환
- 입력한 로또 구매 금액에 대한 유효성 검사 수행
- 숫자로만 이루어져있는가
- 1,000원 단위로 입력하였는가
- 사용자가 입력한 당첨번호를 반환한다
- 입력한 당첨번호에 대한 유효성 검사 수행
- 1 ~ 45사이로 구성된 6개의 숫자인가
- 쉼표로 구분되어있는가
- 사용자가 입력한 보너스 번호 반환
- 입력한 보너스 번호에 대한 유효성 검사 수행
- 1~ 45 사이로 구성된 1개의 숫자인가
- 사용자가 잘못된 값을 입력하는 경우 IllegalArgumentException를 발생시키고 에러 메시지 출력 후 다시 입력을 받기
OutputManager
- 게임 진행 시 필요한 메시지 출력
- 당첨 통계 출력
Lotto
- 로또 번호를 변수로 가지고있는 Lotto 객체 반환
- Lotto 객체 생성시 입력받은 로또 번호들이 6개의 수인지 중복된 수는 없는지 검사
- 당첨번호와 비교하여 로또 번호가 몇등인지 반환
GameManager
- 객채 생성시 구매금액, 로또, 당첨번호, 보너스 번호 초기화
- InputManager, OutputManager를 사용해 구매금액, 당첨번호, 보너스 번호 입력 메시지를 출력하고 사용자의 입력을 반환
- 중복이 없는 랜덤한 1 ~ 45 사이의 6개 로또번호 생성
- 구매한 로또들의 결과를 담은 Map 생성
- 수익률 계산 구현
- 위 기능들을 전체적으로 조합하여 게임을 실행하는 메서드 구현
패키지 구조
lotto
├── enums
│ └── CommonMessages
│ └── LottoRank
├── game
│ ├── Lotto
│ └── GameManager
├── io
│ └── InputManager
│ └── OutputManager
├── Application
└── Runner
이번주도 역시 출력 메시지들은 Enum을 사용해 관리하였다. 또한 추가로 로또 의 당첨 결과를 역시 LottoRank라는 Enum으로 만들어 당첨 상금과 당첨 메시지를 관리하였다.
public enum LottoRank{
NONE(0, null),
THIRD(5000, "3개 일치 (5,000원)"),
FOURTH(50000, "4개 일치 (50,000원)"),
FIFTH_WITHOUT_BONUS(1500000, "5개 일치 (1,500,000원)"),
FIFTH_WITH_BONUS(3000000, "5개 일치, 보너스 볼 일치 (30,000,000원)"),
SIXTH(2000000000, "6개 일치 (2,000,000,000원)");
public int prize;
public String prizeMsg;
LottoRank(int prize, String prizeMsg) {
this.prize = prize;
this.prizeMsg = prizeMsg;
}
}
입출력을 담당하는 InputManager, OutputManager를 담당하면서 만약 실제 서비스라면 단순히 입력을 받고 메시지를 출력하는 IO 객체들은 요청이 올 때마다 생성 할 필요가 있을까 라는 의문이 들었다.
그래서 이번에는 IO 객체인 InputManager와 OutputManager를 싱글톤 패턴으로 구현해 보았다 (우테코 프리코스에서 사용하는 Console 객체는 thread safe 하지 않은 Scanner를 사용하기에 실제 서비스에서 구현한다면 Scanner보다는 BufferReader를 사용해야 할 것 같다.)
public class InputManager {
private InputManager() {}
private static class SingletonHelper {
private static final InputManager inputManager = new InputManager();
}
public static InputManager getInstance() {
return SingletonHelper.inputManager;
}
...
}
public class OutputManager {
private OutputManager() {}
private static class SingletonHelper{
private final static OutputManager outputManager = new OutputManager();
}
}
발생할 수 있는 예외 상황에 대해 고민한다
1주차 2주차 과제들을 진행하면서 느꼈지만 요구사항에서 제시하는 제한사항 외에도 추가적으로 생각해야할 제한사항들이 존재했다. TDD 역시 처음에는 개발자가 실패 테스트케이스를 생각해 내야하기에 이러한 제한사항을 생각해내는 능력 역시 중요한것같다.
객체는 객체스럽게 사용한다
구현을 하면서 힘들었던 중 하나가 해당 class에 어디까지의 책임을 부여할지 정하는것이였다. 이번 피드백에서는 객체가 단순히 Getter로 데이터를 반환하는것이 아닌 메시지를 던지도록 구현하라고 제시하였다. 아직 정확히 감은 안오지필드로 가지고있는 데이터를 단순히 반환하는것이 아니라 데이터로 로직을 수행한 결과를 반환하게 만들라는 의미인것같다.
싱글톤 패턴을 구현 해 보면서 단순히 static을 사용한 방법만 존재하는 줄 았지만 다양한 방법이 존재했으며 내가 지금까지 사용했던 방법은 multi-thread 환경에서는 문제가 발생 할 수 있다는점을 알게 되었다.