3주차 과제 미션은 로또 게임 구현이다. 이번 주차에서는 조금 더 다양한 방법을 이용해서 문제를 해결해볼 생각이다. 아래에 나오는 목차는 개인적인 생각으로 순차적으로 진행된다면 조금 더 쉽게 해결된다는 필자의 판단으로 적은 것이니 참고하면 좋겠다.
2주차 프리코스 숫자야구에서는 기능구현을 작성할 때 UI 로직을 포함하여 입력값, 출력값 등을 구체적으로 적었었다. 하지만 그렇게 작성하는 것보다는 해당 미션에서 필요한 기능들과 필요한 에러처리 정도만 리스트로 나열하고 해당 기능을 구현했으면 체크하는 방식으로 간단하게 작성하였다.
여기서 중요한 점은 기능구현 목록을 작성할 때, 단순하게 기능목록만 작성하는 것이 아니라 Util
클래스와 MVC 패턴
을 이용하는 것이다. Util 클래스 적용
과 MVC 패턴
에 대해서는 아래에서 자세히 설명할 예정이다.
이전에도 말한 바 있지만 기능 구현 목록을 작성하는 것에 있어 시간을 많이 쓰는 것을 아까워하면 안될 것 같다. UI 로직을 짜고 코드를 작성하려고 할 때 여기서 꼬여버리면 뒤에서 시간 낭비가 꽤 심하기 때문이다.
만약 최종 테스트에 간다면...여기서는 구현 목록 짜는데 많은 시간을 쓰면 안되겠지만...연습을 많이 해야겠다.
2차 미션에서도 언급했듯이 상수화는 유지 및 보수하는데 있어서 상당히 유용하다. 하드코딩으로 파일마다 넣다보면 관리하는데 있어서도 어렵고 가독성 측면에서도 좋지 않다.
가장먼저 고정적인 상수와 에러의 종류가 무엇인지를 꼼꼼하게 파악한다.
그리고 해당 조건에 맞는 상수화 작업이 이루어지면 된다. 이 작업은 그렇게 오래 걸리지 않아야 한다.
이번 미션에서 처음으로 Util
클래스를 적용해 보았다.
Util
클래스란 문자열 관련
, 랜덤값 생성
, 날짜 혹은 시간
등 프로젝트 전역에서 사용되는 특정 로직이나 독립적인 기능을 구현한 클래스를 의미한다.
예를 들어 랜덤값을 만드는 경우 RandomUtil
을 만들 수 있다. 그 안에는 랜덤값을 구하는 여러 개의 메서드들이 들어 있을 수 있다. 이런 기능들은 어떤 상태를 가지고 있기 않기 때문에 static
으로 선언되어 사용된다.
정말 주의해야 할 점은 Util
클래스를 만들 때에는 해당 기능이 어디서든 사용 가능해야 하기 때문에 이에 대한 기준이 명확해야 한다.
위에 기능목록에서 Util 클래스를 적용해야 하는 부분은 어디일까?
흔히 우리가 입력을 받고 출력을 하는 부분은 실제 게임이 진행되는 다른 클래스에서 적용하면 된다. Util
클래스에 포함되는 부분은 앞에서 설명한 것과 마찬가지로 랜덤값을 적용하거나, 순위를 반환하기 위해 번호를 카운트하고 같은 번호가 있는지 판단하는 기능과 같이 이후 다른 클래스에서도(전역적으로) 쓰일 수 있는 함수를 넣어두면 된다.
이번 과제에서 쓰인 Util 클래스 파일 몇개를 살펴보자.
앞에 설명에서 말했던 것처럼 예외처리에 관한 Util 클래스를 하나 생성하는 것이 좋다. 어떤 상황일 때 예외처리를 해야하는지를 상황에 따라 함수별로 구체적으로 나눠놓는 것이 좋다.
이번 과제에서는 구매금액에 따른 예외처리, 당첨 번호에 따른 예외처리, 보너스 번호에 따른 예외처리 이렇게 세가지로 나눠서 코드를 작성하였다.
validateWinningNumbers(input) {
const inputNumbers = input.split(",");
if (inputNumbers.length !== StaticNumber.LOTTO_NUMBER_COUNT)
throw new Error(ErrorString.WINNING_NUMBER_COUNT_ERROR);
if (input.replace(/\d|\,/g, "").length > 0)
throw new Error(ErrorString.WINNING_NUMBER_NOT_NUMBER_ERROR);
if (inputNumbers.filter(InputValidator.rangeFilterFunction).length > 0)
throw new Error(ErrorString.WINNING_NUMBER_OUT_OF_RANGE_ERROR);
if (inputNumbers.length !== new Set(inputNumbers).size)
throw new Error(ErrorString.WINNING_NUMBER_DUPLICATE_ERROR);
}
랜덤 값을 생성 하는 것 역시 Util 클래스에 넣어두는 것이 좋다고 했었다. 이번 과제 역시 금액에 따라 여러 개의 숫자 배열로 이루어진 객체를 생성해야하므로 이 과정에서 랜덤 함수를 작성해주는 것이 좋다.
const LottoNumberGenerator = {
generate() {
return Random.pickUniqueNumbersInRange(
StaticNumber.LOTTO_NUMBER_RANGE_START,
StaticNumber.LOTTO_NUMBER_RANGE_END,
StaticNumber.LOTTO_NUMBER_COUNT
);
},
};
여기까지가 기본적으로 이루어져야 하는 작업이다. 이 이후부터는 드디어 MVC 패턴
에 대해서 적용해볼 것이다.
이후에 MVC 패턴에 관한 공부를 한 이후에 관련 포스트를 작성하고자 한다.
MVC 패턴에 좀더 구체적으로 적어놨으니 확인을 해보기를 바란다.
우선 Model
에 포함될 내용에 대해서 생각해보기 전에 이번 미션에서 사용되는 값에 대해 나열을 해봐야 한다.
이렇게 크게는 6가지가 미션에 쓰일 예정이다. 하지만 해당 목록에 대해서 좀더 깊게 생각해보자.
따라서 로또 번호
, 당첨 번호
, 보너스 번호
를 Model에 저장할 것이다. 주로 이 파일들을 domain
이라는 폴더에 저장한다.
구입금액을 입력해 주세요.
8000
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
당첨 번호를 입력해 주세요.
1,2,3,4,5,6
보너스 번호를 입력해 주세요.
7
당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.
위 내용은 과제 설명에 나온 예시의 일부이다. 이것을 바탕으로 UI로직을 정리해볼 수 있다.
const InputView = {
readPurchasePrice(callback) {
this.getUserInput(GuideString.INPUT_PURCHASE_PRICE, callback);
},
readWinningNumber(callback) {
this.getUserInput(GuideString.INPUT_WINNING_NUMBER, callback);
},
readBonusNumber(callback) {
this.getUserInput(GuideString.INPUT_BONUS_NUMBER, callback);
},
getUserInput(guide, callback) {
Console.readLine(guide, (input) => {
callback(input);
});
},
};
각 입력받는 기능을 getUserInput
으로 묶어서 한번에 처리할 수 있도록 처리하였다. 그리고 콜백 함수와 연결을 지어줬다.
const OutputView = {
printPurchaseAmount(amount) {
Console.print(`${amount}개를 구매했습니다.`);
},
printLottos(lottos) {
for (let i = 0; i < lottos.length; i++) {
Console.print(`[${lottos[i].getSortedLottoNumber().join(", ")}]`);
}
},
printRankStatistic(winningStatistic) {
const raps = winningStatistic.length;
for (let i = 0; i < raps; i++) {
Console.print(
StatisticString[i] +
`${winningStatistic[StaticNumber.TOTAL_LOTTO_RANKS - 1 - i]}개`
);
}
},
printRevenueRate(revenueRate) {
Console.print(`총 수익률은 ${revenueRate}%입니다.`);
Console.close();
},
};
출력은 InputView
와는 다르게 공통으로 Console.readLine
을 쓰는 부분이 없기 때문에 각각 다른 함수로 출력할 수 있게끔 하였다.
총 수익률을 출력하는 부분은 로직의 가장 끝부분이기 때문에 Console.close()
를 통해 종료할 수 있게끔 하였다.
controller
에서는 모델에 저장되어 있는 로또 객체 배열, 당첨 번호, 보너스 번호를 이용하여 UI로직에 맞게끔 구현하면 된다.
InputView
에서 구매 금액 입력 메서드를 호출한다.Lotto
객체를 생성한다. ➡️ OutputView
에서 구입한 로또 출력 메서드를 호출한다.InputView
에서 당첨 번호 입력 메서드를 호출한다.InputView
에서 보너스 번호 입력 메서드를 호출한다.OutputView
에서 당첨 통계 출력 메서드를 호출한다.OutputView
에서 총 수익률 출력 메서드를 호출한다.자세한 내용은 [모던 자바스크립트] 클래스에 적어두었습니다.
여기서 간략하게만 정리하자면 private
필드의 선두에는 #
을 붙여준다. private 필드를 참조할 때에도 반드시 #
을 붙여줘야 한다. 이 private 필드는 클래스 내부에서만 참조할 수 있고, 외부에서 직접 접근하는 방법은 존재하지 않는다.
이번 미션에 적용한 클래스 코드를 보여주자면 다음과 같다.
class LottoGameController {
#lottos;
#winningNumbers;
#bonusNumber;
inputWinningNumber() {
InputView.readWinningNumber((input) => {
this.#winningNumbers = new WinningNumbers(input);
this.inputBonusNumber();
});
}
inputBonusNumber() {
InputView.readBonusNumber((input) => {
this.#bonusNumber = new BonusNumber(
input,
this.#winningNumbers.getWinningNumbers()
);
this.displayRankStatistic();
});
}
}
이번 미션에서는 클래스
, MVC 패턴
, props 객체
를 이용하여 코드를 좀더 세분화하고 조직적으로 구성해보자 하였다. 아직 배워야 하는 부분도 많고 다듬어야 하는 부분도 많지만 저런 기술(?)들을 한번은 찍먹이라도 해본 것에 대해 의미를 두고싶다.
앞으로는 generate()
를 이용해 한번 코드를 다듬어보고 싶고 무엇보다 아직은 props 객체에 대해 다루는 것에서 어색함을 느끼기 때문에 체화하는 것에 남은 시간을 쏟고 싶다. 하나의 또 욕심이라면 조금 더 빠른 시간에 기능 구현 목록과 UI로직을 작성하고 싶다.
졸부되세요~!