문제
포비와 크롱이 페이지 번호가 1부터 시작되는 400 페이지의 책을 주웠다. 책을 살펴보니 왼쪽 페이지는 홀수, 오른쪽 페이지는 짝수 번호이고 모든 페이지에는 번호가 적혀있었다. 책이 마음에 든 포비와 크롱은 페이지
번호 게임을 통해 게임에서 이긴 사람이 책을 갖기로 한다. 페이지 번호 게임의 규칙은 아래와 같다.
포비와 크롱이 펼친 페이지가 들어있는 리스트/배열 pobi와 crong이 주어질 때, 포비가 이긴다면 1, 크롱이 이긴다면 2, 무승부는 0, 예외사항은 -1로 return 하도록 solution 메서드를 완성하라.
| pobi | crong | result |
|---|---|---|
| [97, 98] | [197, 198] | 0 |
| [131, 132] | [211, 212] | 1 |
| [99, 102] | [211, 212] | -1 |
| [123, 124] | [95, 96] | ? |
| [401, 402] | [1, 2] | ? |
| [12, 13] | [21, 31] | ? |
| [373, 374] | [235, 236] | ? |
| [98, 97] | [123, 124] | ? |
| [1, 2] | [399, 400] | ? |
| [273, 274] | [372, 373] | ? |
구현 전 고려한 내용
구현 과정 및 피드백 내용
우선 로직을 먼저 글로 작성한 다음 절차적으로 구현을 진행했다.
public class Player {
private final String name;
private final Book book;
private int maxNumber;
public Player(String name) {
this.name = name;
this.book = new Book();
this.maxNumber = 0;
}
public String getName() {
return this.name;
}
public Book getBook() {
return this.book;
}
public int getMaxNumber() {
return this.maxNumber;
}
public void setMaxNumber(int maxNumber) {
this.maxNumber = maxNumber;
}
public void openBook() {
getBook().open();
}
}
플레이어들은 책(Book)을 하나씩 가지고 있다고 생각했다.
또, 게임 실행 시 Player 는 각자의 이름을 부여받아야 이후 플레이어가 추가되어도 수정할 부분이 적을 것이라고 생각해서 name 필드를 만들었다.
❗ 처음에는
player.getBook().open()으로 책을 열어 페이지를 배당받는 기능을 구현하였으나, 디미터의 법칙에 위배된다는 피드백 이후openBook()으로 수정해서 내부를 감추었다.
public class Book {
private String[] selectedPage;
private final int MIN_PAGE = 1;
private final int MAX_PAGE = 400;
private final int NEXT_PAGE = 1;
private final int PREV_PAGE = -1;
private final ParserImpl parse;
public Book() {
selectedPage = new String[2];
parse = new ParserImpl();
}
// 페이지
public void open() {
int randomPage = (int) (Math.random() * MAX_PAGE) + MIN_PAGE;
this.selectedPage = setPages(randomPage);
}
public String[] getSelectedPage() {
return this.selectedPage;
}
private String[] setPages(int randomPage) {
if (isEvenNumber(randomPage)) {
return new String[]{
parse.string(randomPage + PREV_PAGE)
, parse.string(randomPage)
};
}
return new String[]{
parse.string(randomPage)
, parse.string(randomPage + NEXT_PAGE)
};
}
// 짝수이면 true, 홀수이면 false
private boolean isEvenNumber(int randomInt) {
return randomInt % 2 == 0;
}
}
원시값은 무조건 포장했다.
❗
Math.random은 완벽한 난수 생성이 아니라는 지적을 받았다. 시드가 동일하다면Math.random은 항상 같은 값을 리턴하기 때문에 완벽한 난수가 아니라 유사난수로 볼 수 있으며, 보안에 취약하다.
➡ 난수 생성시에는 SecureRandom 을 사용하자!
구문의 타입을 변경하는 '역할'에 의존해야한다고 생각해서, Parser 라는 인터페이스를 만들고 parserImpl 로 그를 구현했다.
parserImpl.java
public class ParserImpl implements Parser {
@Override
public String string(int i) {
return String.valueOf(i);
}
@Override
public int integer(char c) {
return Character.getNumericValue(c);
}
// 오버로딩
public int integer(String str) {
return Integer.parseInt(str);
}
}
숫자를 비교해서 결과를 내는 역할의 메서드들은 NumberComparator 클래스에 작성했다.
getSumDigits(), getMultiplyDigits() : 페이지 문자열을 한 자씩 int 로 바꾸고 각각 덧셈 / 곱셈을 진행한 값을 리턴
getCalcResults() : 왼쪽페이지, 오른쪽페이지 값으로 덧셈 및 곱셈을 진행한 4개 값이 저장된 배열을 리턴
private List<Integer> getCalcResults(String[] selectedPages) {
List<Integer> calcResultList = new ArrayList<>();
for (String page : selectedPages) {
calcResultList.add(getSumDigits(page));
calcResultList.add(getMultiplyDigits(page));
}
return calcResultList;
}
getLargest() : Book 객체가 가지고 있는 페이지 배열로 만들어낼 수 있는 계산 결과 값 중, 가장 큰 값을 리턴
public int getLargest(Book book) {
String[] selectedPages = book.getSelectedPage();
List<Integer> calcResults = getCalcResults(selectedPages);
int maxNumber = Collections.max(calcResults);
return maxNumber;
}
getWinner() : 플레이어 리스트를 순회하면서, 플레이어 객체가 가지고 있는 maxNumber 를 비교하여 가장 큰 값을 가진 플레이어를 리턴
public Player getWinner(List<Player> players) {
Player winner = players.get(0);
int maxNumber = winner.getMaxNumber();
for (int i = 1; i < players.size(); i++) {
Player otherPlayer = players.get(i);
int comparingNumber = otherPlayer.getMaxNumber();
if (comparingNumber > maxNumber) {
winner = otherPlayer;
maxNumber = comparingNumber;
}
}
return winner;
}
solution 은 절차적으로 진행되게 했으며, 메서드명만 봐도 어떤식으로 게임이 진행되는지 알 수 있도록 했다.
게임 실행을 위해서 사용자는 빈 리스트에 Player 객체를 추가해주고, 리스트를 파라미터에 넣어 solution 메서드를 실행해줘야 한다.
Main.java
public class Main {
public static void main(String[] args) {
// 플레이어 추가
List<Player> players = new ArrayList<>();
players.add(new Player("pobi"));
players.add(new Player("crong"));
// 실행
new Controller().solution(players);
}
}
Controller 에는 solution 메서드, 플레이어들이 책을 열어 페이지 배열을 배당받는 메서드, 플레이어들이 페이지 배열을 통해 가장 큰 수를 찾아 배당받는 메서드가 구현되어 있다.
Controller.java
public class Controller {
private final NumberComparator comparator;
private final OutputHandlerImpl output;
public Controller() {
comparator = new NumberComparator();
output = new OutputHandlerImpl();
}
public void solution(List<Player> players) {
selectPagesForPlayers(players);
output.showSelectedPages(players);
setMaxNumbersForPlayers(players);
output.showMaxNumbers(players);
Player winner = comparator.getWinner(players);
output.showResult(winner);
}
private void selectPagesForPlayers(List<Player> players) {
for (Player player : players) {
player.openBook();
}
}
private void setMaxNumbersForPlayers(List<Player> players) {
for (Player player : players) {
int maxNumber = comparator.getLargest(player.getBook());
player.setMaxNumber(maxNumber);
}
}
}
실행결과

플레이어를 추가하는 경우, 결과 정수값은 출력되지 않지만 에러는 발생하지 않는다!
알게된 점
final 은 재할당이 불가능할 뿐, 값 변경이 가능하다.참고 레퍼런스