첫 숙제(?)겸 개인프로젝트를 받게 되었다.
프로젝트는 1단계 2단계 3단계로 구분해서 계산기를 만드는 것이다. 시작해 보자!
당황스럽게도 프로젝트를 생성하자마자 Cannot reslove method라는 오류가 났다.
찾아보니 잊을 만하면 자주 나오는 에러이고 아래와 같이 import가 제대로 되지 않아서 생긴 문제라고 한다.
import org.springframework.beans.factory.annotation.Autowired;
왼쪽 상단의 file -> invalidated caches / Restart을 눌러 캐시를 비우고 재실행하니 해결되었다.
프로젝트 생성부터 오류라니 쉽지 않다.
계산기 1단계는 학습 목표가 아래와 같다
- 변수 & 타입 이해하기
- 연산자 이해하기
- 제어문 & 반복문 이해하기
- 배열 & 컬렉션 이해하기
1단계는 다행스럽게도 막히는 부분 없이 잘 해결해 나갔다.
학습 목표도 이룬 것 같다.
2단계의 학습 목표는 이와 같다
- 클래스 & 메서드 이해하기
- 생성자 & 접근 제어자 이해하기
- static & final 이해하기
- 상속(&포함) & 다형성 이해하기
- Exception & 예외처리 이해하기
이해는 하고 있는 것들이지만 사용을 잘할 수 있을지 모르겠다.
해보자
첫 번째 요구사항
양의 정수 2개와 연산 기호를 매개변수로 받아 사칙연산 수행 후 결과 값을 반환하는 메서드와 연상 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성하라고 하시네요. 그리고 예외들을 적합한 Exception 클래스를 생성해서 throw 하라고 하십니다.
App 클래스에 있는 걸 나누려나 봅니다.
우선 0으로 나누는 예외, 연산 기호가 잘못 들어오는 예제에 대한 Exception 클래스를 생성하였습니다.
public class BadOperationException extends Exception {
public BadOperationException() {
super("연산자를 잘못 입력하셨습니다. 똑바로 입력해 주세요.");
}
}
public class DivideToZeroException extends Exception {
public DivideToZeroException() {
super("0으로 나누셨습니다. 제대로 입력해주세요.");
}
}
그리고 양의 정수 2개와 연산 기호를 매개변수로 받아 사칙연산 수행 후 결과 값을 반환하는 메서드와 연상 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 구현하였습니다.
public class Calculator {
Queue<Double> queue = new LinkedList<>(); // 연산 결과를 저장하는 컬렉션 타입
// throws로 BadOperationException, DivideToZeroException의 예외 사항을 던질 수 있는지 알려줌
public Double calculate(double firstNum, double secondNum, String operation) throws BadOperationException, DivideToZeroException {
double result = 0.0;
switch (operation) {
case "+":
result = firstNum + secondNum;
break;
case "-":
result = firstNum - secondNum;
break;
case "/":
if (secondNum == 0) {
// 상황에 맞게 던지고 메서드 종료
throw new DivideToZeroException();
}
result = firstNum / secondNum;
break;
case "*":
result = firstNum * secondNum;
break;
default:
// 상황에 맞게 던지고 메서드 종료
throw new BadOperationException();
}
return result;
}
}
생각보다 쉽지 않네요
두 번째 요구사항
1단계에서 구현한 main 메서드에 방금 생성한 Calculator 클래스가 활용될 수 있도록 수정하라고 하십니다.
해야죠
일단 Calculator 클래스를 App 클래스에서 활용해야 하니 인스턴스화 먼저 시켜줬습니다.
Calculator calculator = new Calculator();
그리고 아까 구현한 calculate 메서드를 올바른 로직에 매개변수를 넣어주며 실행시킵니다.
while(true) {
System.out.print("첫 번째 숫자를 입력하세요: ");
int firstNum = Integer.parseInt(br.readLine());
System.out.print("두 번째 숫자를 입력하세요: ");
int secondNum = Integer.parseInt(br.readLine());
System.out.print("사칙연산 기호를 입력하세요: ");
String operator = br.readLine();
// result에 calculate에서 return한 값을 저장
double result = calculator.calculate(firstNum, secondNum, operator);
System.out.println(result);
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
if(br.readLine().equals("exit")) {
break;
}
}
오류까지 잘 되는 것을 볼 수 있습니다!
세, 네, 다섯 번째 요구사항
캡슐화를 알아보기 위해 Calculator 클래스에서 연산 결과를 저장하고 있는 Queue에 직접 접근하지 못하도록 하고 메서드를 통해 간접적으로 들고 올 수 있고 수정할 수 있게 변경하라고 합니다.
먼저 다른 클래스에서 접근하지 못하도록 private 제어자를 걸어줬습니다.
private Queue<Double> queue = new LinkedList<>(); // 연산 결과를 저장하는 컬렉션 타입
그리고 간접 접근을 위해 queue를 return해주는 메서드와 가장 처음에 저장한 결과를 지워주는 메서드를 만들었습니다.
public Queue<Double> getResults() {
return queue;
}
public void removeFirstReseult() {
queue.poll();
}
마지막으로 App 클래스에서 사용
System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
if(br.readLine().equals("remove")) {
calculator.removeFirstReseult();
}
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
if(br.readLine().equals("inquiry")) {
System.out.println(calculator.getResults());
}
잘 마무리한 것 같습니다.
여섯 번째 요구사항
Calculator 인스턴스를 생성할 때 생성자를 통해 연산 결과를 저장하고 있는 컬렉션 필드가 초기화 되게 수정하라 합니다.
생성자를 만들어 초기화할 수 있게 구현해 주고
private Queue<Double> queue; // 연산 결과를 저장하는 컬렉션 타입
public Calculator(Queue<Double> queue) {
this.queue = queue;
}
인스턴스를 생성할 때 초기화할 값을 넣어 전달합니다.
Calculator calculator = new Calculator(new LinkedList<>());
일곱 번째 요구사항
Calculator 클래스에 반지름을 매개변수로 전달받아 원의 넓이를 계산하여 반환해 주는 메서드를 구현하라고 하십니다.
private Queue<Double> circleAreaQueue; // 원 넓이 저장하는 컬렉션
// 생성자
public Calculator(Queue<Double> queue, Queue<Double> circleAreaQueue) {
this.queue = queue;
this.circleAreaQueue = circleAreaQueue;
}
// 원의 넓이를 구하는 메서드
public double calculateCircleArea(double radius) {
return radius * radius * 3.14;
}
// 원의 넓이 저장
public void setCircleArea(double circleArea) {
circleAreaQueue.add(circleArea);
}
// 저장된 원의 넓이 return (외부에서 접근할 수 없는 private이기 때문)
public Queue<Double> getCircleArea() {
return circleAreaQueue;
}
여덟 번째 요구사항
사칙연산을 수행하는 계산기 클래스와 원과 관련된 연산을 수행하는 계산기 클래스를 나눠 구현해야 합니다.
구현 후 main 메서드에서 오류가 발생하지 않도록 수정까지 하라 하십니다.
이때까지 해 온 것을 보면 Calculator 함수에 너무 많은 기능들이 있어 원을 계산해 주는 클래스, 사칙연산을 해주는 클래스로 나누려고 하다 보니 Calculator 클래스를 추상클래스로 만들어버리고 원 클래스, 사칙연산 클래스에 상속을 시켜야겠다는 생각이 들었다.
하지만
// 원 계산
public abstract Double calculate(double radius)
// 사칙 연산 계산
public abstract Double calculate(double firstNum, double secondNum, String operator)
calculate 메서드를 하나만 쓰기 위해서는 매개변수를 통일시켜야 했다.
진짜 너무 떠오르지 않아 고민하던 중 같이 캠프를 진행하고 있는 한 분 께서 getter, setter을 써서 해보라고 힌트를 주셨다.
그래서
public abstract Double calculate()
이런 식으로 추상 메서드를 선언해 주고
사칙연산 클래스에는 아래와 같이 setter로 입력받을 때 값을 들고 왔다.
private double firstNum;
private double secondNum;
private String operator;
// 입력받아 firstNum, secondNum, operator를 저장
public void setValues(String firstNumStr, String secondNumStr, String operator)
또한 원 클래스에도 마찬가지로 setter를 사용하여 입력받을 때 값을 들고 왔다.
사칙 연산은 너무 길어 다 들고 오지 않았지만 원 클래스는 조금 더 코드를 들고 왔다.
private Double radius;
@Override
public Double calculate() {
return radius * radius * 3.14;
}
// 입력을 받아 radius를 저장
public void setRadius(String radius) throws BadNumException {
try {
this.radius = Double.parseDouble(radius);
} catch (NumberFormatException e) {
throw new BadNumException();
}
}
이게 생각 안 나서 고민을 엄청하고 시간도 엄청나게 썼었는데 힌트 주셨을 때 너무 감사했다.
다신 안 까먹을 것 같다.
아홉 번째 요구사항
연산 메서드가 무거워 사칙연산 각각의 기능을 분리하여 Class로 만들고 작동하게 해야 합니다.
@Override
public Double calculate() throws BadOperationException, DivideToZeroException {
switch (operator) {
case "+":
result = addOperator.operate(firstNum, secondNum);
break;
case "-":
result = subtractOperator.operate(firstNum, secondNum);
break;
case "/":
if (secondNum == 0) {
// 상황에 맞게 던지고 메서드 종료
throw new DivideToZeroException();
}
result = divideOperator.operate(firstNum, secondNum);
break;
case "*":
result = multiplyOperator.operate(firstNum, secondNum);;
break;
default:
// 상황에 맞게 던지고 메서드 종료
throw new BadOperationException();
}
return result;
}
클래스를 나눠서 진행하였다.
열 번째 요구사항
%연산을 구현해야 한다. 하지만 지금 구조에서 효율적으로 변경하라고 하신다.
interface로 operator를 만들면 어떨까라는 생각이 든다. 어짜피 Calculate를 implements 받는 연산이면 계산하는 메서드는 무조건 들어가야 하므로 다형성을 극대화하여 개발코드 수정을 줄이고 유지보수성을 높였다.
물론 이번 프로젝트는 작아서 더 복잡해질 수도 있지만 배운다는 목적으로 구현했다.
인터페이스 구현
public interface Calculate {
public abstract double calculate(double firstNum, double secondNum);
}
인터페이스를 implements(이행)하여 연산자 별 클래스 생성
public class ModOperator implements Calculate {
@Override
public double calculate(double firstNum, double secondNum) {
return firstNum % secondNum;
}
}
public class AddOperator implements Calculate {
@Override
public double calculate(double firstNum, double secondNum) {
return firstNum + secondNum;
}
}
'스파르타 코딩클럽(Java+Spring과정) > 개인 프로젝트' 카테고리의 다른 글
나만의 일정 관리 앱 서버 만들기 (0) | 2024.05.17 |
---|