이전 TIL에서 자바 계산기 프로젝트의 리팩토링 과정을 통해 코드 가독성과 유지보수성을 크게 개선했다. 이번 TIL에서는 그 리팩토링된 코드를 더 개선하여 모듈화한 과정을 공유하려 한다. 모듈화를 통해 각 클래스에 역할을 명확히 부여하고, 코드를 더 깔끔하고 유지보수하기 쉽게 만들었다.
리팩토링을 통해 기능별로 메서드를 분리했지만, 여전히 하나의 클래스에서 모든 기능을 처리하고 있어 책임 분리가 명확하지 않았다. 이로 인해 코드가 길어지고 유지보수가 어려워지는 문제가 남아 있었다.
이번에는 입력 처리, 연산 처리, 결과 관리 기능을 각각 독립적인 클래스로 나누어 역할을 분리했다. 이를 통해 각 클래스가 하나의 책임만 가지도록 만들었다.
package Calculator_Lv2;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
Calculator calc = new Calculator();
Scanner sc = new Scanner(System.in);
while (true) {
int num1 = getInputNumber(sc, "첫 번째 숫자를 입력하세요(0을 포함한 양의 정수): ");
int num2 = getInputNumber(sc, "두 번째 숫자를 입력하세요(0을 포함한 양의 정수): ");
char operator = getOperator(sc);
try {
calc.setResults(num1, num2, operator);
System.out.println("결과: " + calc.getResults());
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
continue;
}
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
String nextLine = sc.next();
if (nextLine.equals("exit")) {
System.out.println("계산기를 종료합니다.");
break;
}
}
sc.close();
}
private static int getInputNumber(Scanner sc, String prompt) {
int num;
while (true) {
System.out.print(prompt);
if (sc.hasNextInt()) {
num = sc.nextInt();
if (num >= 0) {
break;
} else {
System.out.println("0 또는 양의 정수를 입력해주세요.");
}
} else {
System.out.println("잘못된 입력입니다. 정수를 입력해주세요.");
sc.next();
}
}
return num;
}
private static char getOperator(Scanner sc) {
char operator;
while (true) {
System.out.print("사칙연산 기호를 입력하세요(+, -, *, /): ");
operator = sc.next().charAt(0);
if ("+-*/".indexOf(operator) != -1) {
break;
} else {
System.out.println("잘못된 연산 기호입니다. 다시 입력해주세요.");
}
}
return operator;
}
}
package Calculator_Lv2;
public class Calculator {
private double result;
public void setResults(int num1, int num2, char operator) {
switch (operator) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다.");
}
result = num1 / (double) num2;
break;
default:
throw new IllegalStateException("잘못된 연산자입니다.");
}
}
public double getResults() {
return result;
}
}
package Calculator_Lv2;
public class App {
public static void main(String[] args) {
InputHandler inputHandler = new InputHandler();
Calculator calc = new Calculator();
while (true) {
int num1 = inputHandler.getInputNumber("첫 번째 숫자를 입력하세요(0을 포함한 양의 정수): ");
int num2 = inputHandler.getInputNumber("두 번째 숫자를 입력하세요(0을 포함한 양의 정수): ");
char operator = inputHandler.getOperator();
try {
double result = calc.performOperation(num1, num2, operator);
System.out.println("결과: " + result);
System.out.println("결과 리스트: " + calc.getResults());
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
continue;
}
System.out.println("결과 리스트의 첫 번째 값을 삭제하시겠습니까(yes 입력 시 삭제)");
String response = new java.util.Scanner(System.in).nextLine();
if (response.equalsIgnoreCase("yes")) {
calc.removeResults();
}
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
String exitResponse = new java.util.Scanner(System.in).nextLine();
if (exitResponse.equals("exit")) {
inputHandler.closeScanner();
break;
}
}
}
}
package Calculator_Lv2;
import java.util.ArrayList;
import java.util.List;
public class Calculator {
private final List<Double> results = new ArrayList<>();
private final Operation operation = new Operation();
public double performOperation(int num1, int num2, char operator) {
double result = operation.calculate(num1, num2, operator);
addResult(result);
return result;
}
public void addResult(double result) {
results.add(result);
}
public List<Double> getResults() {
return new ArrayList<>(results);
}
public void removeResults() {
if (!results.isEmpty()) {
results.remove(0);
} else {
throw new IndexOutOfBoundsException("삭제할 연산 결과가 없습니다.");
}
}
}
package Calculator_Lv2;
public class Operation {
public double calculate(int num1, int num2, char operator) {
switch (operator) {
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
if (num2 == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다.");
}
return (double) num1 / num2;
default:
throw new IllegalStateException("잘못된 연산자입니다.");
}
}
}
package Calculator_Lv2;
import java.util.Scanner;
public class InputHandler {
private final Scanner sc = new Scanner(System.in);
public int getInputNumber(String prompt) {
int num;
while (true) {
System.out.print(prompt);
if (sc.hasNextInt()) {
num = sc.nextInt();
if (num >= 0) {
break;
} else {
System.out.println("0 또는 양의 정수를 입력해주세요.");
}
} else {
System.out.println("잘못된 입력입니다. 정수를 입력해주세요.");
sc.next();
}
}
return num;
}
public char getOperator() {
char operator;
while (true) {
System.out.print("사칙연산 기호를 입력하세요(+, -, *, /): ");
operator = sc.next().charAt(0);
if ("+-*/".indexOf(operator) != -1) {
break;
} else {
System.out.println("잘못된 연산 기호입니다. 다시 입력해주세요.");
}
}
return operator;
}
public void closeScanner() {
sc.close();
}
}
모듈화 후 각 클래스는 명확한 역할을 가지게 되어, 코드의 책임이 분리되었다. InputHandler
는 입력 처리, Calculator
는 연산 및 결과 관리, Operation
은 실제 연산을 처리하는 데 집중할 수 있다.
모듈화를 통해 기능별로 클래스가 독립적으로 동작하도록 분리되었기 때문에 유지보수가 쉬워지고, 기능 확장 시에도 각각의 모듈만 수정하면 된다.
입력 검증과 연산 처리를 각각의 모듈로 분리함으로써 중복된 코드를 제거하고, 필요한 부분만 재사용할 수 있다.
이번 모듈화를 통해 코드의 가독성, 유지보수성, 확장성 측면에서 큰 개선을 이루었다. 클래스별로 역할이 명확해졌고, 앞으로 새로운 기능을 추가하거나 변경할 때 더 쉽게 작업할 수 있게 되었다.