1. 작성 목적
2. 피드백 요약 (핵심 피드백은 🔥표시)
3. 핵심 피드백 분석
3-1. 🔥Enum 클래스 내부로 계산 로직 이동 및 잘못된 구조 사용
EnumClass 내부에 선언하여 불필요한 중첩 구조 생성EnumClass.OperatorType 형태로 접근해야하는 불편함 public class EnumClass {
public enum OperatorType{
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE
}
}
연산 타입을 정의했음에도 불구하고 실제 연산 로직은 ArithmeticCalculator에서 switch-case로 처리
연산 타입과 실제 동작이 분리됨
새로운 연산 추가 시 Switch 수정 필요
public double calculate(T a, T b, EnumClass.OperatorType type) {
//제네릭 매개변수는 직접적으로 연산을 할 수가 없어서 해당 메서드 사용.
double num1 = a.doubleValue();
double num2 = b.doubleValue();
double result;
switch (type) {
case ADD:
result = num1 + num2;
resultList.add(result);
return result;
case SUBTRACT:
result = num1 - num2;
resultList.add(result);
return result;
case MULTIPLY:
result = num1 * num2;
resultList.add(result);
return result;
case DIVIDE:
if (num2 == 0) {
System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
return 0;
}
result = num1 / num2;
resultList.add(result);
return result;
default:
return 0;
}
}
연산 실행도 main 클래스에서 처리
while (true) {
System.out.print("사칙연산 기호를 입력하세요(+, -, *, /) : ");
String lengthCheck = sc.next();
if (lengthCheck.length() != 1) {
System.out.println("연산자는 한 글자만 입력하세요.");
continue;
}
char str = lengthCheck.charAt(0);
OperatorType.OperatorType type;
switch (str) {
case '+':
type = OperatorType.OperatorType.ADD;
break;
case '-':
type = OperatorType.OperatorType.SUBTRACT;
break;
case '*':
type = OperatorType.OperatorType.MULTIPLY;
break;
case '/':
type = OperatorType.OperatorType.DIVIDE;
break;
default:
System.out.println("잘못된 연산자입니다.");
continue;
}
result = arithmeticCal.calculate(num1, num2, type);
break;
}
기존 ArithmeticCalculator 에서 연산하던 것을 개선하여 Enum 내부 메서드에 정의
public enum OperatorType {
ADD('+') {
@Override
public double calculate(double num1, double num2) {
return num1 + num2;
}
},
SUBTRACT('-'){
@Override
public double calculate(double num1, double num2){
return num1 - num2;
}
},
MULTIPLY('*'){
@Override
public double calculate(double num1, double num2){
return num1 * num2;
}
},
DIVIDE('/'){
@Override
public double calculate(double num1, double num2){
if (num2 == 0)
{
throw new ArithmeticException("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
return num1 / num2;
}
};
private final char type;
OperatorType(char type){
this.type = type;
}
public abstract double calculate(double num1, double num2);
public static OperatorType searchtype(char type){
switch (type){
case '+':
return OperatorType.ADD;
case '-':
return OperatorType.SUBTRACT;
case '*':
return OperatorType.MULTIPLY;
case '/':
return OperatorType.DIVIDE;
default:
throw new IllegalArgumentException("잘못된 연산자입니다.");
}
}
}
while (true) {
System.out.print("사칙연산 기호를 입력하세요(+, -, *, /) : ");
String lengthCheck = sc.next();
if (lengthCheck.length() != 1) {
System.out.println("연산자는 한 글자만 입력하세요.");
continue;
}
char str = lengthCheck.charAt(0);
//enum에서 throw로 "에러 메세지"를 던지고 catch로 받아서 출력
try {
OperatorType type = OperatorType.searchtype(str);
result = type.calculate(num1, num2);
break;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
https://velog.io/@gpekd5/Java-%EB%AC%B8%EB%B2%95-enum-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0
2-1) 객체로 동작
2-2) 클래스처럼 필드, 생성자, 메서드를 가질 수 있다.
private final char symbol;OperatorType(char symbol) {
this.symbol = symbol;
}public char getSymbol() {
return symbol;
}2-3) 생성자는 외부에서 호출 할 수 없다.
Enum의 객체는 직접 생성할 수 없고, 정의된 값만 사용할 수 있다.
OperatorType op = OperatorType.ADD; // 가능
// new OperatorType() → 불가능
Enum은 단순한 값이 아니라 행동(로직)을 포함할 수 있다.
public enum OperatorType {
ADD {
public double apply(double a, double b) {
return a + b;
}
},
SUBTRACT {
public double apply(double a, double b) {
return a - b;
}
};
public abstract double apply(double a, double b);
}
3-2. 🔥ArrayList가 아닌 List 타입으로 선언
ArrayList<Integer> results = new ArrayList<>();
List<Integer> results = new ArrayList<>();
=> 왜 List numbers = new ArrayList<>(); 이런 방식을 사용하는가?
=> ArrayList numbers = new ArrayList<>(); 이런 방식은 왜 안쓰는가??
=> 자바에서는 보통 인터페이스로 선언하고, 구현체로 생성하는 방식을 사용한다.
=> List는 인터페이스이다. "이러한 기능을 제공해야한다"는 규칙을 가짐
=> List 인터페이스를 구현한 클래스
=> List가 규칙이라면 ArrayList는 규칙을 실제로 만든 구체적인 코드이다.
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
=> numbers 변수가 ArrayList 타입으로 고정
numbers = new LinkedList<>(); // 오류
=> 이런식으로 바꾸려고하면 오류 발생 (다른 클래스이기 때문)
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
=> numbers 변수가 List<>타입이므로 다른 클래스도 담을 수 있다.
numbers = new LinkedList<>();
=> 이런식으로 구현 가능
=> 변수 이름은 그대로 두고 구현체만 바꿀 수 있다.
=> 같은 부모 타입으로 여러 자식 객체를 다룰 수 있는 것
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new LinkedList<>();
=> 둘 다 타입은 List<Integer> 이나 실제 객체는 다름
public void printNumbers(ArrayList<Integer> list) {
System.out.println(list);
}
=> 해당 메서드는 ArrayList만 받을 수 있다.
public void printNumbers(List<Integer> list) {
System.out.println(list);
}
=> 해당 메서드는 ArrayList도 받을 수 있고, LinkedList도 받을 수 있다.
=> 재사용성이 더 좋다.
3-3. 🔥🔥제네릭 설계 부족
입력 단계부터 double로 고정하는 것이 아니라 Number 을 통해 타입을 유지하도록 변경
입력받아 타입을 구분하는 방식으로 수정
public static Number parseNumber(String input) {
if (input.contains(".")) {
return Double.parseDouble(input);
}
return Integer.parseInt(input);
}
ArithmeticCalculator.java에서는 여러 제네릭 타입을 사용하여 다른 타입을 가지도록 구현하였다.
또한, 타입에 따라 연산을 분기하여 입력값이 모두 정수인 경우에는 정수 연산을 수행하고 이외에는 실수 연산을 하였다.
결과는 Number 타입으로 선언하여 정수와 실수 결과를 모두 담을 수 있도록 하였다.
public <U extends Number, V extends Number> Number calculator(U num1, V num2, OperatorType type ){
Number result;
if (num1 instanceof Integer && num2 instanceof Integer){
result = type.calculate(num1.intValue(), num2.intValue());
}else {
result = type.calculate(num1.doubleValue(), num2.doubleValue());
}
resultList.add(result);
return result;
}
OperatorType.java 파일에서는 메서드 오버로딩하여 같은 calculate 메서드를 int용 / double용으로 나눠서 처리하였다.
public enum OperatorType {
ADD('+') {
@Override
public int calculate(int num1, int num2) {
return num1 + num2;
}
@Override
public double calculate(double num1, double num2) {
return num1 + num2;
}
},
SUBTRACT('-'){
@Override
public int calculate(int num1, int num2){
return num1 - num2;
}
@Override
public double calculate(double num1, double num2){
return num1 - num2;
}
},
MULTIPLY('*'){
@Override
public int calculate(int num1, int num2){
return num1 * num2;
}
@Override
public double calculate(double num1, double num2){
return num1 * num2;
}
},
DIVIDE('/'){
@Override
public int calculate(int num1, int num2){
if (num2 == 0)
{
throw new ArithmeticException("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
return num1 / num2;
}
@Override
public double calculate(double num1, double num2){
if (num2 == 0)
{
throw new ArithmeticException("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
return num1 / num2;
}
};
private final char type;
OperatorType(char type){
this.type = type;
}
public abstract int calculate(int num1, int num2);
public abstract double calculate(double num1, double num2);
public static OperatorType searchType(char type){
return switch(type){
case '+' -> ADD;
case '-' -> SUBTRACT;
case '*' -> MULTIPLY;
case '/' -> DIVIDE;
default -> throw new IllegalArgumentException("잘못된 연산자입니다.");
};
}
}
=> 제네릭은 클래스나 메서드에서 사용할 `타입을 미리 정하지 않고, 사용 시점에 타입을 결정할 수 있도록 하는 기능
📌 이번 과제에서는 <U, V> 처럼 여러 제네릭 타입을 사용하여 서로 다른 타입의 조합 처리
=> Number는 Integer, Double, Float 등의 부모 클래스이며, 다양한 숫자 타입을 하나로 묶어 처리할 수 있다.
📌 Number 타입은 직접 연산 불가능 !! => intValue(), doubleValue() 등의 메서드를 통해 변환 후 연산
=> 같은 이름의 메서드를 매개변수 타입이나 개수를 다르게 하여 여러 개 정의하는 것
📌 적용 예시
calculate(int, int)
calculate(double, double)
=> 객체가 특정 타입인지 확인하는 연산자
📌 적용 예시
if (num1 instanceof Integer && num2 instanceof Integer)
4. 회고 및 느낀점
🌭 처음 도전 과제를 구현할 때 enum과 제네릭에 대한 이해가 충분하지 않은 상태에서 개발을 진행하였고,
그로 인해 요구사항과 맞지 않는 아주 아쉬운 코드가 작성되었다.
이후, 튜터님의 피드백을 바탕으로 개념을 다시 학습하고 코드를 전면적으로 수정하는 과정을 거쳤다.
이번 과제를 통해 단순히 기능을 구현하는 것이 아니라, 다형성과 재사용성을 고려한 설계가 중요하다고 느껴졌다.
앞으로도 코드를 작성할 때 유지보수성과 확장성을 함께 고민하는 습관을 가져야겠다고 생각했다.
이전 장비 개발 업무에서는 이미 만들어진 구조 위에 기능을 추가하는 경우가 많았지만,
앞으로는 직접 구조를 설계하는 경험을 통해 좋은 설계가 무엇인지에 대해서도 계속 고민해봐야겠다.