“exit”
문자열을 입력하기 전까지 무한으로 계산을 진행할 수 있도록 소스 코드를 수정하기현재 프로젝트에서 만족했고, 앞으로의 훈련기간에서 지속하고 싶은 부분을 작성했다.
현재 프로젝트에서 어려웠던 점과 아쉬웠던 점을 작성했다.
### [LEVEL1] 프로그램 실행예시
---------------------------------------
첫번째 숫자를 입력하세요. (exit 입력 시 종료): 1
두번째 숫자를 입력하세요. (exit 입력 시 종료): 2
사칙연산 기호를 입력하세요! (+, -, *, /): *
결과 (1 * 2): 2
첫번째 숫자를 입력하세요. (exit 입력 시 종료):
두번째 숫자를 입력 받고나서 사칙연산을 받을 때, 사칙연산에 자동으로 공백이 들어가는 오류가 있었다.
// 사용자의 숫자 입력 값을 검증하는 메서드
private static int readInt(Scanner scanner) {
// 입력 값이 "exit"일 경우 프로그램을 종료합니다.
if (scanner.hasNext("exit")) {
System.out.println("프로그램을 종료합니다.");
System.exit(0);
}
// 입력 값이 숫자가 아닐 경우 다시 입력 받도록 합니다.
while (!scanner.hasNextInt()) {
System.out.print("올바른 숫자를 입력하세요!: ");
scanner.next(); // 잘못된 입력 제거
}
// 입력 값이 올바를 경우, 반환합니다.
return scanner.nextInt();
}
숫자를 입력받으면서 exit
를 입력했을 때 프로그램 종료
기능을 수행하고 싶어서 흐름대로 해당 메서드에 넣었는데,
해당 메서드에서 프로그램 종료
기능이 들어가는 것 자체가 메서드의 책임을 벗어난다는 피드백을 받았다. 사용자의 숫자 값을 검증하는 메서드에서 종료기능이 같이 들어가는 것이 알맞지 않다는 뜻이였다.
또한 메서드 네이밍도 조금 더 생각해보면 좋을 거 같다고 하셨다.
어떻게 보면 네이밍이 제일 어려운 듯.. 😂
nextInt()
는 입력값으로 들어온 값 중에 Enter
나 공백
을 기준, 그 앞의 Int형 값
을 가져온다. 버퍼에 남아있는 값은 Enter(공백)
인데, 그 값이 그대로 사칙연산 값에 들어가는 것이였다. 해당 문제는 스캐너.nextLine();
한 줄을 추가해서 해결할 수 있었다. 버퍼에 남아 있는 값을 지워주는 역할을 한다!Level2에서 프로그램 종료 기능은 Calculator
클래스에 넣었다.
// ✅ 계산기 종료 메서드
public void exitCalculate(){
outputPrinter.printExitPrompt();
// 프로그램 종료
System.exit(0);
}
기능별로 분리하기 위해 io.input
패키지를 생성하였고, InputReader
클래스에 메서드를 분리하였다.
getIntInput()
로 네이밍을 하였다.
package level2.io.input;
public class InputReader {
// Scanner 객체 생성
private static final Scanner scanner = new Scanner(System.in);
// ✅ 사용자의 숫자 입력 값을 검증하는 메서드
public int getIntInput() {
// 입력 값이 숫자가 아닐 경우 다시 입력 받음
while (!scanner.hasNextInt()) {
System.out.print("올바른 숫자를 입력하세요!: ");
scanner.next(); // 잘못된 입력 제거
}
// 입력 값이 숫자일 경우, 변수에 숫자 저장
int number = scanner.nextInt();
// 개행 문자 제거
scanner.nextLine();
// 입력 값이 올바를 경우, 반환
return number;
}
. . .
}
### [LEVEL2] 프로그램 실행예시
---------------------------------------
[ 계산기 프로그램 ]
1. 계산하기
2. 출력하기(All)
3. 삭제하기
4. 종료하기
선택:
package level2.calculator;
public class Calculator {
// 사칙연산 기호와 Operation 객체를 매핑하는 Map 컬렉션
private final Map<String, Operation> operations;
// ✅ 생성자
public Calculator() {
// FIXME: 추후 레벨 3에서 수정이 필요한 부분
// 사칙연산 기호와 Operation 객체를 생성 및 연결
operations = Map.of(
"+", new Addition(),
"-", new Subtraction(),
"*", new Multiplication(),
"/", new Division()
);
}
package level2.operations;
public interface Operation {
int calculate(int firstNumber, int secondNumber);
}
package level2.operations;
public class Addition implements Operation {
@Override
public int calculate(int firstNumber, int secondNumber) {
return firstNumber + secondNumber;
}
}
Map
컬렉션을 사용하여 사용자의 연산기호와 연산하는 객체를 매핑Calculate
클래스 생성할 때, 사용자의 연산기호와 알맞는 연산 객체를 매핑Operation
함수형 인터페이스로 구현Calculate
클래스에서는 operations.calculate()
로 계산Map
컬렉션을 사용하는 부분을 Enum
을 활용하면 좋겠다는 피드백operations
패키지 안에 사칙연산 별 클래스파일이 있어서 유지보수 및 파일이 불필요하게 많다는 피드백Level3에서 Map 컬렉션
→ Enum 클래스
사용
연산기능(ADD, SUBTRACT, MULTIPLY, DIVIDE)을 Enum에서 정의
사용자의 입력값으로 알맞는 연산기능을 찾는 메서드 생성 (findByOperator)
Enum 클래스의 calculate()
메서드를 통해 Calculator 클래스에서는 호출만 하여 연산 가능하도록 함
// Calculator 클래스에서 사용하는 방법
Operator operator = Operator.findByOperator(userOperator);
return operator.calculate(firstNumber, secondNumber);
package level3.operations;
public enum Operator {
// ✅ADD, SUBTRACT, MULTIPLY, DIVIDE Enum 객체 생성
ADD("+", ((firstNumber, secondNumber) -> firstNumber + secondNumber)),
SUBTRACT("-", ((firstNumber, secondNumber) -> firstNumber - secondNumber)),
MULTIPLY("*", (firstNumber, secondNumber) -> firstNumber * secondNumber),
DIVIDE("/", (firstNumber, secondNumber) -> {
if (secondNumber == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다.");
}
return firstNumber / secondNumber;
});
private final String symbol; // ✅연산자 기호 (+, -, *, /)
private final Operation operation; // ✅연산 기능 (함수형 인터페이스)
// ✅생성자
Operator(String userOperator, Operation operation) {
this.symbol = userOperator;
this.operation = operation;
}
// ✅Getter || 연산자 기호(Operator) 반환 메서드
public String getSymbol() {
return symbol;
}
// ✅연산 메서드
public int calculate(int firstNumber, int secondNumber) {
return operation.calculate(firstNumber, secondNumber);
}
// ✅사용자로부터 Operator 객체를 찾아 반환하는 메서드
public static Operator findByOperator(String userOperator) {
return Arrays.stream(values()) // 1. enum의 모든 항목(ADD, SUBTRACT, MULTIPLY, DIVIDE)을 Stream 으로 변환
.filter(op -> op.symbol.equals(userOperator)) // 2. op.symbol == userOperator 인 경우를 찾음
.findFirst() // 3. 찾은 첫 번째 요소 반환
.orElseThrow(() -> new IllegalArgumentException("올바르지 않은 연산자입니다: " + userOperator)); // 4. 찾지 못한 경우 예외처리 (inputReader 에서 예외처리하므로 발생확률 ↓)
}
}
### [LEVEL3] 프로그램 실행예시
---------------------------------------
[ 정수 계산기 프로그램 ]
1. 계산하기
2. 출력하기(All)
3. 삭제하기
4. 종료하기
선택:
과제제출 기한이 별로 남지 않아서 제대로 받지 못했다...
getNumberInput()
메서드는 제네릭을 사용하면서 프로그램 시작 할 때 1~4의 int형 정수를 받기도 하면서, 계산기능을 수행할 때는 int, double 타입 관계없이 숫자면 모두 받을 수 있게 하고 싶었다.이건 아닌데..
라는 생각이 들었다. // ✅ 사용자의 숫자 입력 값을 검증하는 제네릭 메서드
@SuppressWarnings("unchecked")
public <T extends Number> T getNumberInput() {
return getValidInput(() -> {
// hasNextDouble()은 입력값이 double, int 일 경우 true 반환
if (!scanner.hasNextDouble()) {
scanner.next(); // 잘못된 입력 제거
return null; // getValidInput() 메서드에서 error message 출력
}
// 입력값이 int, double 일 경우 변수에 저장
double number = scanner.nextDouble();
// 개행 문자 제거
scanner.nextLine();
// ✅ 해당 메서드의 반환 값은 제네릭 T 타입을 유지하면서 Integer 타입만 반환하고 싶음
// 1️⃣ Integer.valueOf((int) number)는 Integer 타입으로 형변환
// 2️⃣ 하지만 컴파일은 T 타입으로 반환하라고 요구함 (T extends Number)
// 3️⃣ 따라서 (T)로 캐스팅하여 제네릭 타입을 유지해야 함
// 4️⃣ 결국, 반환값은 Integer 이지만, 제네릭 T로 변환하여 반환
// 💡 (T)는 컴파일러에게 "이 값은 T 타입이야!"라고 알려주는 역할을 함
return (T) Integer.valueOf((int) number);
}, "올바른 숫자를 입력하세요!: ");
}
/**
* ✅입력값을 검증하고 유효한 값이 입력될 때까지 반복하는 제네릭 메서드
* (Supplier<T>는 매개변수를 받지 않고 새로운 값을 반환하는 기능에 적합한 함수형 인터페이스)
*
* @param inputProvider 입력을 처리하는 함수 (유효한 값이면 반환, 무효한 값이면 null 반환)
* @param errorMessage 잘못된 입력이 들어왔을 때 출력할 메시지
* @return 검증된 유효한 입력값 (T)
*/
private <T> T getValidInput(Supplier<T> inputProvider, String errorMessage) {
while (true) {
// 함수의 결과값을 변수에 저장
T input = inputProvider.get();
// 변수가 유효하면 return
if (input != null) {
return input;
}
// 변수가 null 이면 다시 입력 요청
System.out.print(errorMessage);
}
}
다음 프로젝트에서 시도해볼 점들을 작성했다.