-
Notifications
You must be signed in to change notification settings - Fork 92
[그리디] 서현진 로또 미션 3, 4, 5단계 제출합니다. #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요! 로또 미션 리뷰어를 맡게 된 조승현입니다.
추석 연휴 잘 보내고 계신가요? 올려주신 코드에서 궁금한 지점이 보여 코멘트를 남겨보았습니다. 현진님의 생각을 자유롭게 알려주시면 되세요!
AutoLotto와 ManualLotto는 Lotto를 상속 받고 있는데 현재 코드에선 상속의 큰 장점이 보이지 않습니다. 근데 두 클래스가 Lotto와 관련성이 매우 깊어서 상속으로 묶어주면 좋겠다고 생각했는데 어떤 로직으로 묶으면 좋을 지 잘 모르겠습니다.
어떤 로직으로 묶어주어야 할지 보이지 않는 이유는 부모 객체인 Lotto 자체가 도메인 로직을 갖지 않은 채 자료구조만 가지고 있는 값 객체이기 때문입니다.
Lotto를 값 객체만으로 바라본다면, Lotto를 상속한 AutoLotto와 ManualLotto를 Lottos에서 List lottoList라는 하나의 필드에서 관리할 수 있게 된 것만으로도 상속의 장점을 충분히 누리고 계신 겁니다. LottoMatcher에서 로또 당첨금을 계산할 때, autoLotto 따로 manualLotto 따로 바라볼 필요 없이 lottoList라는 통일된 자료 하나만으로 작업할 수 있게 된 것 자체가 장점이라 생각합니다. 현재 미션에서 의도하는 것이 딱 그정도이기도 하구요.
src/main/java/View/InputView.java
Outdated
validate(money); | ||
return money; | ||
} catch (InputMismatchException e) { | ||
System.out.println("금액은 숫자로 입력해주세요!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
System.out.println보다 더 직관적으로 에러를 표현하는 System 메서드가 있습니다. 한번 찾아보세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
적용해보겠습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/main/java/View/InputView.java
Outdated
Scanner scanner = new Scanner(System.in); | ||
|
||
public int getMoney() { | ||
System.out.println("구입금액을 입력해 주세요."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
입력을 요청하는 문구도 표준 출력에 해당한다고 하면 InputView보다 OutView에서 담당하는 것이 더 알맞아 보입니다. 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 코드를 짜며 이 부분을 고민 했습니다! 다만 위와 같은 방식으로 사용한 이유는 만약 outview에서 어떤 값을 입력 받을 지에 대해 출력하는 기능을 가지고 있다면 컨트롤러에서 보다 혼잡스러운 코드 진행이 될 것 이라고 생각했습니다!
InputView inputView = new InputView();
OutView view = new OutView();
view.printGetMoney();
int money = inputView.getMoney();
view.printManualLottoNumber();
int manualLottoNumber = inputView.getManualLottoNumber();
또한 inputView에서 어떤 값을 입력 받아야 하는지 알려주는 기능이기 때문에 가지고 있어도 된다고 생각했습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
입력을 담당하는 객체와 출력을 담당하는 객체를 왜 분리하면 어떤 게 좋을까? 라는 질문을 한번 던져보세요!
현재 로또미션에서는 사용자에게 메시지를 응답하는 수단으로 표준 출력(콘솔 출력)을 채택하고 있습니다.
하지만 실세계에서는 응답 수단이나 형태가 정말 자주 변합니다. 예를 들어 로또 메시지를 응답하는 수단으로 더이상 표준 출력을 쓰지 않고 슬랙 API로 HTTP 요청을 보내는 수단을 사용할거야! 라는 요구사항이 들어온다 해봅시다.
그럼 System.out(or err).println을 사용하는 코드들을 모두 찾아다니며 메시지 표현 코드를 바꾸어주어야 합니다.
지금처럼 InputView에서 출력을 담당하는 형태에서는 OutView 뿐만 아니라 InputView도 바꾸어야 하니 응집도가 떨어집니다. 출력을 OutView에서만 진행했다면 OutView만 바꾸어주면 되는데 말이지요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
하지만 그럼에도 불구하고 컨트롤러가 복잡해지는 게 싫고, 값을 입력받기 위한 helptext 정도는 InputView에서 받고 싶다 하신다면
입출력 수단을 인터페이스화해서 분리해보세요!
public class InputView {
private final Scanner scanner; // 인터페이스
private final Printer printer; // 인터페이스
public int getMoney() {
printer.print("구입금액을 입력해 주세요.");
while (true) {
try {
int money = scanner.scanInteger();
validate(money);
return money;
} catch (InputMismatchException e) {
printer.print("금액은 숫자로 입력해주세요!");
} catch (IllegalArgumentException e) {
printer.print(e.getMessage());
}
}
}
구조를 위와 같은 형태로 잡으면 현진님이 원하시는 InputView의 역할을 유지하면서도 입출력의 상세 구현 방법은 인터페이스 하나에 응집시킬 수 있습니다. 수단이 변경되더라도 인터페이스의 구현체만 바꾸어주면 되니 편리하겠지요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
출력에 관한 모든 메소드를 outputView에 위치하도록 수정해봤습니다!
manualLottoLines.add(scanner.nextLine()); | ||
} | ||
return manualLottoLines; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로또 범위 초과나 중복 번호 입력에 대한 검증이 없는 것 같습니다.
[1, 2, 3, 4, 6, 86]
[4, 5, 6, 2, 7, 7]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가해보겠습니다
|
||
OutView outView = new OutView(); | ||
|
||
outView.printLottosStaus(lottos.getAutoLottoCount(money, manualLottoNumber), manualLottoNumber); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'로또 개수'에 대한 용어로 lottoCount와 lottoNumber가 혼용되는 것 같습니다.
변수명만 보았을 때 로또 번호인지 개수인지 파악하기 어렵네요. 통일해주세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵!! 통일했습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
public String[] getJackpotNumber() { | ||
System.out.println("정답 로또번호를 입력해주세요( ','로 구분해서 입력)"); | ||
return scanner.nextLine().split(","); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
금액 정보를 받을 땐 InputView에서 int로 변환하여 반환하셨는데, 로또번호를 입력받을 땐 구분자로 나누기만 하고 int 변환은 하지 않으셨네요! 이유가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String[] 으로 받고 interger로 변환하는 로직은 jackpot에 있어야 한다고 생각했습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇게 생각하신 이유를 알려주셔야죠!
지금 로또 금액을 입력받을 땐 잘못된 입력이 들어왔을 때 오류 메시지도 바로 내뱉고, 입력도 다시 받는 반면
수동 로또 번호와 당첨번호를 받을 때는 도메인 객체 생성 이전까지 오류 메시지를 보여주지도 않으며 오류 발생시 애플리케이션이 그냥 꺼져버립니다.
코드 구조가 비즈니스 요구사항을 앞서면 절대 안됩니다. 사용자는 애플리케이션이 잘 동작하는지만 보지, 코드 구조같은 것에 관심없어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
최대한 수동로또를 한개 만들 때마다 검증로직을 적용해서 바로 콘솔에 나오게 해보고 싶었습니다! 하지만 자꾸 바로 콘솔에 나오지 않고 입력받은 수동로또를 다 생성후 수동로또 타당성검증 메소들에서 throw한 예외들이 한번에 catch되는 것 같습니다! 이에 대해 어떻게 코드 구조를 바꿔야하는지 잘 모르겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outView.promptForManualLotto();
List<String> rawNumbersList = inputView.manualLotto(manualLottoCount);
Lottos lottos = new Lottos();
for (String s : rawNumbersList) {
try{
lottos.addLotto(new ManualLotto(s));
}
catch (IllegalArgumentException e)
{
System.err.println("[error] :" + e.getMessage());
}
}
이 코드의 흐름을 바꾸어보면 될 것 같아요. 수동 로또 목록을 List로 한 번에 받는 대신
수동 로또 하나 입력받기 -> 로또 객체 만들기 -> 로또s에 담기
이걸 한 사이클로 만들어서 수동로또 개수만큼 반복하는거죠.!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수동로또를 잘못 입력했을 때 입력을 다시 받게 하려면 어떻게 하면 좋을지도 한번 생각해서 구현해보세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
친절한 설명 감사합니다! 리뷰를 토대로 리팩토링 해봤습니다!
acc8b99
} | ||
List<Integer> numbers = parseNumbers(splits); | ||
validateNumbers(numbers); | ||
Collections.sort(numbers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자바에서 제공하는 어떤 자료구조를 활용하면 중복제거와 원소 정렬을 동시에 할 수 있습니다. 한 번 찾아보세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set을 이용해서 구현해보았습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set 쓰신 건 저도 압니다. 중복제거와 원소 정렬을 동시에 할 수 있습니다.
라는 말은 Collections.sort(numbers)
를 쓰지 않아도 원소 정렬을 기본 지원해주는 자료구조가 있으니 찾아보라는 의미입니다. 자바에서 제공하는 자료구조가 꽤 다양해요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 찾아보니 treeset이 말씀하신 자료구조 인 것 같아 사용하여 구현해보았습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘 하셨습니다. 추가로 set의 add 메서드는 boolean을 반환합니다. 이 boolean은 무엇을 의미하고, 이를 어떻게 활용해볼 수 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예시코드) numbers.add(number);
add 메소드는 collection에서 처음 정의가 되고 set에서 오버라이딩 하는 형태로 구성되어 있는 것 같습니다!
add메소드의 return 값의 정의(?) : 이 컬렉션에 변화가 존재하면 true를 return 한다.
- collection 구현
collection.add
- 파라미터 값을 가지고 있지 않으면 (
collection.contain==false
)true
를 반환 - 파라미터 값을 가지고 있고, 이 컬렉션이 복제를 금지한다면 (
collection.contain==true
)false
를 반환
- 파라미터 값을 가지고 있지 않으면 (
- set 구현
set.add
- (참인 경우) 컬렉션과 동일
- set은 같은 원소를 가지지 않기 때문에 오직 (
collection.contain==true
)으로만 판단
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
활용해서 코드 리팩토링 해봤습니다!
36937b0
src/main/java/Model/Lottos.java
Outdated
} | ||
|
||
public List<Lotto> getLottoList() { | ||
return Collections.unmodifiableList(lottoList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getLottoList()를 호출하는 코드들을 보면,
for (Lotto lotto : lottos.getLottoList())
처럼 원소 순회에만 사용하고 있습니다.
어떤 인터페이스를 활용하면 for (Lotto lotto : lottos)
이렇게 간략히 쓸 수 있지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
너무 좋은 방식인 것 같습니다! 다만 궁금한 내용은 iterable과 읽기 전용 리스트로 반환하는 차이가 제가 보기엔 iter 구문에서 좀 더 간편해진다 밖에 없어 보이는데 어떤 장점이 또 있는지 궁금합니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'순회'에 한정했을 때 객체의 내부 자료구조를 캡슐화할 수 있다는 장점이 있습니다!
Getter를 사용하는 경우 lottos의 List라는 실제 자료구조가 외부로 노출되는 반면, iterator를 사용하여 순회하면 lottos의 내부 자료구조가 List인지 Set인지 알 필요없이 Lotto라는 내부 원소와 순회에만 집중할 수 있습니다.
Iterator 패턴을 자세히 설명한 블로그가 있으니 찾아보시면 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
블로그 보고 공부해보겠습니다!
this.matchCounts = new HashMap<>(); | ||
|
||
for (Lotto lotto : lottos.getLottoList()) { | ||
int matches = countMatches(jackpot.getJackpot(), lotto.getLotto()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LottoMatcher의 역할이 lottos의 번호들과 Jackpot 번호를 대조해보는 것이라면, 이 작업 전체를 그냥 Lottos 객체 안에서 수행해도 되지 않을까요? Matcher가 꼭 분리되어 존재해야 하는 이유가 궁금합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
과제 요구사항에서 하나의 클래스 안에선 메소드가 10개 이하를 유지하기 위해서 클래스를 분리해서 만들어보았습니다!! 또한 기능 상 Lottos가 정답과 비교하는 기능 까지 가지고 있으면 너무 많은 책임을 가지고 있는 것 같아서 matcher라는 클래스를 생성해보았습니다!
private int bounsNumber; | ||
|
||
public Jackpot(String[] inputJackpot, int bounsNumber) { | ||
JackpotGenerator from = JackpotGenerator.from(inputJackpot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jackpot 생성작업을 Jackpot 안에 두지 않고 Generator로 분리하신 이유가 궁금해요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jackpot => jackpotgenerator + bounsNumber 를 관리하는 클래스
jackpotgenerator => 사용자로 부터 정답 로또를 입력받는 클래스
jackpot은 보너스 넘버까지 같이 관리하려고 나눴습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ManualLotto의 경우에는 숫자 검증, 중복 숫자 검증과 같이 객체를 만드는 데에 필요한 검증 작업과 숫자 생성 작업이 생성자 안에 들어가 있습니다.
반면 Jackpot은 생성자에서 진행하지 않고 검증 작업과 숫자 생성 작업을 Generator로 분리하셨지요. 두 객체의 생성 방법에 차이를 두신 이유가 궁금한 것입니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔔 요거 답을 안 주셨어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아아 죄송합니다!
다른 의미는 크게 없었고 정적팩토리 메소드를 한번 코드에 적용시켜 보고 싶어서 코드를 generator로 구현해봤습니다!
안녕하세요 조승현 리뷰어님!
이번 로또미션 매칭된 서현진 리뷰이입니다!
열심히 해보겠습니다!
🔢 미션 요구사항
🕹️ 게임 동작 흐름 (Game Workflow)
LottoController
를 중심으로 로또 게임은 다음 순서로 진행됩니다.InputView
를 통해 사용자로부터 로또 구입 금액을 받습니다.InputView
를 통해 수동으로 구매할 로또의 개수와 번호를 입력받습니다.Lottos
모델을 생성합니다. 이 과정에서 사용자가 입력한 수동 로또와 나머지 금액에 해당하는 자동 로또가 함께 생성됩니다.OutView
를 통해 구매한 수동 및 자동 로또의 개수와 전체 로또 번호를 화면에 출력합니다.InputView
를 통해 지난 주 당첨 번호 6자리와 보너스 번호를 입력받습니다.Jackpot
(당첨 번호)과Lottos
(구매한 로또)를 사용하여LottoMatcher
를 생성합니다.LottoMatcher
는 생성과 동시에 모든 로또의 당첨 여부를 계산하여 통계를 냅니다.OutView
가LottoMatcher
로부터 최종 당첨 통계와 수익률을 받아 화면에 출력합니다.코드 구성
어려운 점