[STUDY] Class 3

joy0987·2023년 7월 28일

스터디

목록 보기
2/4

문제

🚀 기능 요구 사항

포비와 크롱이 페이지 번호가 1부터 시작되는 400 페이지의 책을 주웠다. 책을 살펴보니 왼쪽 페이지는 홀수, 오른쪽 페이지는 짝수 번호이고 모든 페이지에는 번호가 적혀있었다. 책이 마음에 든 포비와 크롱은 페이지
번호 게임을 통해 게임에서 이긴 사람이 책을 갖기로 한다. 페이지 번호 게임의 규칙은 아래와 같다.

  1. 책을 임의로 펼친다.
  2. 왼쪽 페이지 번호의 각 자리 숫자를 모두 더하거나, 모두 곱해 가장 큰 수를 구한다.
  3. 오른쪽 페이지 번호의 각 자리 숫자를 모두 더하거나, 모두 곱해 가장 큰 수를 구한다.
  4. 2~3 과정에서 가장 큰 수를 본인의 점수로 한다.
  5. 점수를 비교해 가장 높은 사람이 게임의 승자가 된다.
  6. 시작 면이나 마지막 면이 나오도록 책을 펼치지 않는다.

포비와 크롱이 펼친 페이지가 들어있는 리스트/배열 pobi와 crong이 주어질 때, 포비가 이긴다면 1, 크롱이 이긴다면 2, 무승부는 0, 예외사항은 -1로 return 하도록 solution 메서드를 완성하라.

제한사항

  • pobi와 crong의 길이는 2이다.
  • pobi와 crong에는 [왼쪽 페이지 번호, 오른쪽 페이지 번호]가 순서대로 들어있다.

실행 결과 예시

pobicrongresult
[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]?


구현 전 고려한 내용

🤔 공통 체크리스트

  1. 이전 스터디 내용을 생각했는가, 적용했는가?
  2. 모든 변수들을 통제하였는가?
  3. 컨벤션을 잘 지켰는가?
  4. 내가 짠 코드에 어느 부분이 객체지향적인가?
  5. 명확한 표현을 했는가?
  6. 가독성이 좋은가?
  7. 중복을 제거했는가?
    ➡ 내가 구현한 방식에서는 왼쪽 페이지, 오른쪽 페이지의 중복이 발생할 확률이 0% 이므로 중복을 제거하지 않았다.

개인적으로 고려한 내용

  1. 메서드명 동사로
  2. 참조는 한 번만 (디미터의 법칙 준수)
  3. 입출력 클래스는 인터페이스로 구현
  4. 배열이나 콜렉션 변수명에 Array, List 등 붙이지 말고 복수형으로 만들기
  5. 플레이어가 추가되는 경우 수정할 부분을 최소화 - new Player 만 하면 되도록


구현 과정 및 피드백 내용

우선 로직을 먼저 글로 작성한 다음 절차적으로 구현을 진행했다.

생각한 로직

  • 1부터 400 사이의 정수 중에 하나를 랜덤으로 생성한다.
  • 해당 정수가 홀수인 경우 +1, 짝수인 경우 -1한 정수와 같이 문자열 배열에 담는다.
  • 페이지 배열을 각 플레이어가 가진 Book 의 selectedPage 필드에 저장한다.
  • 각 플레이어들의 페이지로 가질 수 있는 가장 큰 수를 구하고, 플레이어의 maxNumber 필드에 저장한다.
  • 플레이어 간 maxNumber 값을 비교해서, 가장 큰 수를 가진 플레이어를 winner 이름의 새로운 플레이어 객체로 생성한다.
  • winner 의 이름이 'pobi'이면 1 출력, 'crong'이면 2 출력, 무승부는 0 출력

1. Player 클래스 구현

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() 으로 수정해서 내부를 감추었다.



2. Book 클래스 구현

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 을 사용하자!

3. Parser 클래스 구현

구문의 타입을 변경하는 '역할'에 의존해야한다고 생각해서, 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);
    }
}

4. NumberComparator 서비스 클래스 구현

숫자를 비교해서 결과를 내는 역할의 메서드들은 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;
    }

5. solution 메서드 구현

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);
        }
    }
}


실행결과

플레이어를 추가하는 경우, 결과 정수값은 출력되지 않지만 에러는 발생하지 않는다!



알게된 점

  1. 일급 컬렉션을 사용하자!
  • Collection을 Wrapping하면서 그 외의 다른 멤버 변수가 없는 상태

  1. final 은 재할당이 불가능할 뿐, 값 변경이 가능하다.

  1. 팩토리 메소드 패턴
  • 객체 생성을 공장(Factory) 클래스로 캡슐화 처리하여 대신 생성하게 하는 생성 디자인 패턴 (객체 간의 결합도가 낮아지고 유지보수가 용이!)

  1. 개선된 switch문 (JAVA 14)
  • 다중 case 를 사용할 수 있다. (, 로 구분)
  • 화살표 라벨 case OOO -> 마지막에 break; 를 사용한 것과 동일
  • 화살표 라벨을 사용할 때, 실행문이 두 줄 이상일 때는 중괄호 필수!

  1. static 의 장단점
    장점
  • 고정된 메모리 영역을 사용하기 때문에 매번 인스턴스를 생성하며 낭비되는 메모리를 줄일 수 있다
  • 객체를 생성하지 않고 사용 가능하기 때문에 속도가 빠르다
    단점
  • 프로그램 종료 시 까지 메모리에 할당되어 있다 (프로그램 퍼포먼스 악영향)
  • 객체지향적이지 못하다
  • 정적 메서드는 Interface 를 구현하는 데 사용될 수 없다 (재사용성 하락)

  1. sealed (JAVA 17)
  • 상속하거나, 구현할 클래스를 지정해두고, 해당 클래스들만 상속/구현이 가능하도록 제한하는 기능
  • super-class 에 sealed 키워드를 사용하고, permits 키워드 뒤에 해당 클래스를 상속받을 sub-class 를 선언하면 된다
  • permits 선언되지 않은 클래스들은 super-class 를 상속하거나 구현할 수 없다


참고 레퍼런스

profile
아자아자

0개의 댓글