우테코 기간에 했던 미션들을 다시 리팩터링하고 객체 지향 프로그래밍에 대해 연습해보고자 2주차에 진행 하였던 숫자 야구 게임에 대해 적어보겠습니다.
먼저 아래와 같은 기능 요구사항이 있는데 어떠한 개념들이 어떤 역할을 하는지 정리해봅시다.
🚀 기능 요구 사항
기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
- 예) 상대방(컴퓨터)의 수가 425일 때
- 123을 제시한 경우 : 1스트라이크
- 456을 제시한 경우 : 1볼 1스트라이크
- 789를 제시한 경우 : 낫싱
- 예) 상대방(컴퓨터)의 수가 425일 때
- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
첫번째 개념 - 게임
게임의 역할은 게임을 시작하고 재시작과 종료를 하는 역할을 한다.
두번째 개념 - 상대방 or 컴퓨터
필자는 컴퓨터로 정의 하였다.
컴퓨터의 역할은 1 부터 9 까지 중복되지 않은 숫자 3개를 생성하는 역할을 한다.
세번째 개념 - 플레이어
플레이어는 콘솔 창에 1 부터 9 까지 중복되지 않은 숫자 3개를 생성하는 역할을 한다.
네번째 개념 - 심판
심판은 컴퓨터가 생성한 숫자 게임 리스트와 플레이어가 입력한 숫자 리스트를 비교하여 판단하는 역할을 한다.
다섯번째 개념 - 비교자
플레이어 숫자와 컴퓨터 숫자를 비교하는 역할을 한다.
이제 개념들을 정리 해보았으니 메시지를 정해보자
게임을 시작하라 와 게임을 재시작 할 건지 종료할건지 라는 메시지를 선택한다.
public boolean start(Player player) {
return false;
}
public boolean isRestartOrEnd(boolean isPass) {
return false;
}
비교를 시작하라라는 메시지를 선택한다.
public boolean compareStart(Player player) {
return false;
}
비교하라는 메시지를 선택한다.
public boolean compare(Player player) {
return false;
}
비교 대상을 비교하라는 메시지를 선택한다.
public boolean compareTo(List<Integer> computerNumbers, Player player) {
return false;
}
숫자를 선택하라는 메시지를 선택한다.
public List<Integer> selectNumbers(String readNumbers) {
return Collections.emptyList();
}
숫자를 생성하라는 메시지를 선택한다.
public List<Integer> generate() {
return Collections.emptyList();
}
처음 미션을 진행했을 당시의 구성도는 아래와 같다.
이러한 구성도는 심판 플레이어 컴퓨터 중 하나만 코드를 변경해버리면 게임을 빈번히 수정해야하는 문제점이 있고 객체 지향 스럽지가 않다.
위에서 정의한 개념과 메시지를 통해 구성도를 다시 그려보자.
조금 더 객체 지향 스럽게 구성이 되었고 한 개념이 변경이 되도 여러군데를 손봐야하는 문제는 사라진다.
이제 java 언어로 구현을 해보자.
- Player.class
public class Player {
public List<Integer> selectNumbers(String readNumbers) {
return Arrays.stream(readNumbers.split(""))
.map((Integer::parseInt))
.collect(Collectors.toList());
}
}
콘솔 창에서 입력을 받아 리스트로 변환해준다
- BaseBallNumberGenerator.class
public class BaseBallNumberGenerator implements NumbersGenerator {
@Override
public List<Integer> generate() {
List<Integer> numbers = new ArrayList<>();
while (numbers.size() < 3) {
int number = Randoms.pickNumberInRange(1, 9);
if (!numbers.contains(number)) {
numbers.add(number);
}
}
return numbers;
}
}
- Compare.class
public class Compare {
private int strikeCount;
private int ballCount;
public boolean compareTo(List<Integer> computerNumbers, Player player) {
while (!isPass()) {
List<Integer> playerNumbers = player.selectNumbers(InputView.readNumbers());
strikeOrBallCount(computerNumbers, playerNumbers);
OutputViewService.of(strikeCount, ballCount).viewByCount();
}
return isPass();
}
/// private method 생략
}
isPass() 메서드는 stirkeCount가 3일 시 count 를 reset 해주고 true를 반환해준다.
reset을 해주는 이유는 플레이어가 숫자를 입력하면 strike와 ball에 대해 count가 올라간다.
count 출력을 하고 reset을 해주지 않으면 count 가 누적이 되어 값이 일치하지 않는다.
strikeOrBallCount() 메서드는 간단하게 strike와 ball 조건에 만족이 되면 count 해주는 메서드다.
OutputViewService.of(strikeCount, ballCount).viewByCount() 메서드는 count 조건에 맞는 view를 출력해준다.
- Computer.class
public class Computer {
private final NumbersGenerator generator;
private final Compare compare;
public Computer(NumbersGenerator generator, Compare compare) {
this.generator = generator;
this.compare = compare;
}
public boolean compare(Player player) {
return compare.compareTo(generator.generate(), player);
}
}
- Referee.class
public class Referee {
private final Computer computer;
public Referee(Computer computer) {
this.computer = computer;
}
public boolean compareStart(Player player) {
return computer.compare(player);
}
}
- Game.class
public class Game {
private final Referee referee;
public Game(Referee referee) {
this.referee = referee;
}
public boolean start(Player player) {
OutputView.printStart();
boolean isPass = referee.compareStart(player);
OutputView.printEnd();
return isPass;
}
public boolean isRestartOrEnd(boolean isPass) {
int num = Integer.parseInt(InputView.readRestartOrEnd());
if (num == 1) {
return isPass;
}
return !isPass;
}
}
입출력 view 는 제외하고 도메인에 대해 구현을 집중적으로 하였고 객체 지향의 책임 주도 설계가 부족하다 느껴 연습해보았습니다.
부족한 부분과 각 객체에 책임과 역할이 맞지 않는다면 피드백 부탁드립니다.
'Laguage' 카테고리의 다른 글
[Java] 로또 (객체 지향 편) (0) | 2022.11.29 |
---|---|
[Java] 지네릭 변성 (0) | 2022.08.13 |
[Java] Decorator Pattern (0) | 2022.07.16 |
[Java] Exception (0) | 2022.07.15 |
[Java] Stream (0) | 2022.07.14 |
[Java] Lambda expression (0) | 2022.07.14 |
[Java] Inner Class (0) | 2022.07.14 |
[Java] Generic Programing (0) | 2022.07.14 |