728x90
서론
- 이제 실제 콘솔 애플리케이션이 동작하도록 실제 View와 Controller의 결합을 해보도록 하겠습니다.
View
- 실제 사용자가 값을 입력하고, 로직이 실행된 뒤의 결과를 출력하기 위한 계층으로 우리는 콘솔 애플리케이션이니 입력은 Scanner 를 통해 받고, 출력은 System.out.println 을 통해 합니다.
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class InputView {
private static final Scanner scanner = new Scanner(System.in);
private static final String INPUT_DELIMITER = "";
private InputView() {
}
public static int inputMenu() {
System.out.println("게임을 새로 시작하려면 1, 종료하려면 9를 입력하세요.");
return scanner.nextInt();
}
public static List<Integer> inputNumbers() {
System.out.print("숫자를 입력해주세요 : ");
String inputNumbers = scanner.next();
return Arrays.stream(inputNumbers.split(INPUT_DELIMITER))
.map(Integer::parseInt)
.toList();
}
}
- Scanner는 새롭게 매번 생성할 필요가 없습니다. 해당 시스템에서 최초 한번만 생성하면 되기 때문에 상수로 처리합니다.
- 여기서 가장 고민했던 부분이 “입력된 값을 DTO로 View에서 감싸는게 맞는가?”에 대한 고민이였습니다.
- 명확한 정답은 없지만, 실제 View를 프론트라고 생각했을때 백엔드 애플리케이션이 실행되는 사이에서 DTO로 변환한다고 생각을 했고, 여기서는 그냥 단순히 값만 넘겨주는것으로 하였습니다.
import baseball.dto.CheckBallResponse;
public class OutputView {
private OutputView() {
}
public static void printExitMessage() {
System.out.println("애플리케이션이 종료되었습니다.");
}
public static void printResult(CheckBallResponse checkBallResponse) {
if (checkBallResponse.isNotting()) {
System.out.println("낫싱");
return;
}
System.out.println(checkBallResponse.ballCount() + "볼 " + checkBallResponse.strikeCount() + "스트라이크");
if (checkBallResponse.isSuccess()) {
System.out.println("3개의 숫자를 모두 맞히셨습니다.");
System.out.println("-------게임 종료-------");
}
}
public static void printPickComputerNumbers() {
System.out.println("컴퓨터가 숫자를 뽑았습니다.");
}
public static void printErrorMessage(String message) {
System.out.println(message);
}
}
- 출력도 printResult가 메인인데, 우리가 실제 웹 애플리케이션에서 생각해봣을때도 프론트에서도 분기를 쳐서 보여주는 행동들을 하기 때문에 if문을 통해 처리하도록 하였습니다.
Application
package baseball;
import baseball.controller.BaseBallGameController;
import baseball.domain.Commend;
import baseball.dto.CheckBallResponse;
import baseball.dto.CheckBallsRequest;
import baseball.generator.BaseBallNumberGenerator;
import baseball.generator.BaseBallNumberShuffleGenerator;
import baseball.repository.ComputerRepositoryImpl;
import baseball.view.InputView;
import baseball.view.OutputView;
import java.util.List;
public class GameApplication {
private static final BaseBallNumberGenerator baseBallNumberGenerator = new BaseBallNumberShuffleGenerator();
private static final BaseBallGameController baseBallGameController = new BaseBallGameController(new ComputerRepositoryImpl());
public static void run() {
Commend commend = Commend.END;
try {
do {
commend = Commend.of(InputView.inputMenu());
int computerId = baseBallGameController.computerStart(baseBallNumberGenerator);
OutputView.printPickComputerNumbers();
gameInProgress(computerId);
}
while (commend != Commend.END);
applicationEnd();
} catch (Exception e) {
OutputView.printErrorMessage(e.getMessage());
run();
}
}
private static void applicationEnd() {
OutputView.printExitMessage();
}
private static void gameInProgress(int computerId) {
try {
boolean isFinished = true;
while (isFinished) {
List<Integer> userNumbers = InputView.inputNumbers();
CheckBallsRequest checkBallsRequest = new CheckBallsRequest(userNumbers, computerId);
CheckBallResponse checkBallDto = baseBallGameController.checkBalls(checkBallsRequest);
OutputView.printResult(checkBallDto);
isFinished = !checkBallDto.isSuccess();
}
} catch (Exception e) {
OutputView.printErrorMessage(e.getMessage());
gameInProgress(computerId);
}
}
}
- 해당 메서드에선 각 View Controller를 연결해서 처리하는 행위를 해줍니다.
- 실제 웹 애플리케이션과는 다르지만, 여기서 최대한 사용자 입력과 출력에 대한 부분들을 Controller에게 전달하는 것을 목표로 하였습니다.
- 이렇게 따로 main메서드가 아닌 다른것으로 빼둔 이유는 차후 통합테스트를 작성을 목표로 하고 있어, 용이하게 하기 위함입니다.
- 여기서는 baseBallNumberGenerator가 여기에 존재하는데 이 이유는 제어할수 없는 값은 최 상단 계층으로 올려 의존이 최소한이 되도록 하는것이 목표이기 때문에 이와 같은 로직으로 작성되었습니다.
public class Application {
public static void main(String[] args) {
GameApplication.run();
}
}
- 마지막 main 메서드에서는 GameApplication의 run 메서드만 호출하면 끝입니다.
간단한 회고
- 아주 간단한 로직이 있는 게임을 만들어보는 경험을 해봤습니다.
- 4년전 우아한테크코스 프리코스에서 진행했던 미션이였는데, 다시 함으로써 기억이 새록새록 났던거 같습니다.
- 최대한 객체지향적으로 생각하면서 하려고 하니 간단한 로직이지만 복잡성은 있었다고 생각이 들지만, 그만큼 차후 Step2, Step3를 할때 편리함을 느끼고 있습니다.
- 아울러 Step1을 할때 후회되었던 부분이 있습니다.
- Computer를 Repository에 넣는게 아니라 Game이라는 객체를 만들어 Computer와 PlayerRecord(유저의 번호, 결과 등)의 Collection을 가지고 있어 처리하는 방식이 훨씬 더 확장성이 있고 더 의미있는 객체지향적인 사고였던거 같습니다.
- Step2에서 같이 리팩토링을 진행할 예정입니다.
- 아울러 테스트 코드의 커버리지를 신경쓰면서 진행 하면서 안정적인 애플리케이션을 목표로 가는것이 좋았습니다.
결론
- 이제 다음 Step2로 넘어갑니다.
- Step2의 요구사항은 종료가 되기 전까지 게임을 계속 할수 있는데, 종료된 게임의 기록을 볼수 있는 기능입니다. 또한 시작시간과, 종료시간, 횟수를 추가함으로써 진행된 Game의 추가정보를 볼수 있습니다.
- 간단한 와이어 프레임을 같이 첨부합니다.
게임을 새로 시작하려면 1, 기록을 보려면 2, 종료하려면 9을 입력하세요.
1
컴퓨터가 숫자를 뽑았습니다.
숫자를 입력해주세요 : 123
1볼 1스트라이크
숫자를 입력해주세요: 145
1볼
숫자를 입력해주세요: 671
2볼
숫자를 입력해주세요: 216
1스트라이크
숫자를 입력해주세요: 713
3스트라이크
3개의 숫자를 모두 맞히셨습니다.
-------게임 종료-------
게임을 새로 시작하려면 1, 기록을 보려면 2, 종료하려면 9을 입력하세요.
2
게임 기록
[1] / 시작시간: 2024. 04. 07 23:12 / 종료시간: 2024. 04. 07 23:13 / 횟수: 5
1
1번 게임 결과
숫자를 입력해주세요 : 123
1볼 1스트라이크
숫자를 입력해주세요: 145
1볼
숫자를 입력해주세요: 671
2볼
숫자를 입력해주세요: 216
1스트라이크
숫자를 입력해주세요: 713
3스트라이크
3개의 숫자를 모두 맞히셨습니다.
-------기록 종료-------
게임을 새로 시작하려면 1, 기록을 보려면 2, 종료하려면 9을 입력하세요.
9
애플리케이션이 종료되었습니다
- 전체적인 Step1의 애플리케이션의 코드는 아래서 확인가능합니다.
- 이 글과 관련된 피드백이 있다면 언제든 댓글로 남겨주세요!
728x90
728x90
'Backend > Java' 카테고리의 다른 글
Five API Performance Optimization Tricks that Every Java Developer Must Know(모든 자바 개발자가 알아야 할 다섯 가지 API 성능 최적화 팁) (0) | 2024.04.20 |
---|---|
[숫자야구게임 Step2] 정책을 정의내리고 테크스펙을 작성해보자. (0) | 2024.04.20 |
[숫자야구게임 Step 1] 비지니스 로직을 작성하자. (0) | 2024.04.13 |
[번역] Memory-Hogging Enum.values() Method(메모리를 과도하게 사용하는 Enum.values() 메서드) (0) | 2024.04.12 |
[숫자야구게임 Step1] 도메인 구현 (0) | 2024.04.12 |