어제 간단한 계산기를 구현했다. 두개의 수와 사칙연산자를 입력받아, 연산을 수행하고 저장공간에 저장하고, 저장공간 제어까지.
남겨진 글과는 달라진 수정사항이 몇가지 있다.
System.out.println("사칙연산 기호를 입력하세요: ");
String operator = sc.nextLine();
---->
System.out.println("사칙연산 기호를 입력하세요: ");
char operator = sc.next().charAt(0);
과제 요구사항에 맞게 자료형을 String에서 char로 수정.
System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
if(sc.nextLine().equals("remove")) {
System.out.println("가장 먼저 저장된 "+numQ.peek()+" 를 삭제합니다.");
}
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
if(sc.nextLine().equals("inquiry")) {
System.out.println("저장된 연산결과를 불러옵니다.");
for (int num : numQ) {
System.out.println(num);
}
}
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
if (sc.nextLine().equals("exit")) {
System.out.println("계산기를 종료합니다.");
flag = false;
}
수행기능에 따른 출력문구를 추가,
연산결과 삭제시 remove() 메소드를 삭제와 동시에 반환도 해주는 peek()으로 대체.
이제 해보자. level 2
App.main 메소드의 연산 부분을 클래스로 따로 빼왔다.
public class Calulator {
Queue<Double> numQ = new LinkedList<Double>();
public double calculate(int firstNumber, int secondNumber, char operator) {
// 결과값을 담을 변수 선언
double result = 0;
// 연산자에 따른 연산방법
switch (operator) {
case '+':
result = firstNumber + secondNumber;
break;
case '-':
result = firstNumber - secondNumber;
break;
case '*':
result = firstNumber * secondNumber;
break;
case '/':
if (secondNumber != 0) {
result = firstNumber / secondNumber;
} else {
System.out.println("0으로 나눌수 없습니다.");
result = -1;
}
break;
}
// 연산방법 제어 끝
numQ.add(result);
return result;
}
}
예외처리를 위한 클래스를 생성하고,
public class BadInputException extends Exception{
public BadInputException(String type) {
super("잘못된 입력입니다! " + type + "를 입력해주세요!");
}
}
3주차 강의에서 봤던 클래스 코드를 가지고 왔다.
예외처리가 필요한 부분을 정리해보면,
App.main
- 입력값이 정수가 아닐때
- 입력값이 4가지 사칙연산 기호가 아닐때 (+, -, *, /)
Calculator
- 나눗셈 연산일때 분모가 0일때
먼저 App.main의 입력 부분 예외처리.
사칙연산자는 4가지 문자를 저장한 List를 선언해 내장된 contain() 메소드를 이용해 날로먹었다.
public class App {
public static void main(String[] args) throws Exception {
...
List<Character> operlandList = new ArrayList<Character>
(Arrays.asList(new Character[]{'+', '-', '*', '/'}));
... // 사칙연산자 후보리스트,
System.out.println("첫 번째 숫자를 입력하세요: ");
Integer firstNumber = sc.nextInt();
if (firstNumber < 0 || !(firstNumber instanceof Integer)) {
throw new BadInputException("정수");
}
System.out.println("사칙연산 기호를 입력하세요: ");
Character operator = sc.next().charAt(0);
if (!operlandList.contains(operator)) { // 후보리스트에 존재하지 않은 입력 값이면 false를 반환
throw new BadInputException("사칙연산자");
}
System.out.println("두 번째 숫자를 입력하세요: ");
Integer secondNumber = sc.nextInt();
if (secondNumber < 0 || !(secondNumber instanceof Integer)) {
throw new BadInputException("정수");
}
이렇게 하면 일단은 예외처리를 할 수 있겠으나, 정수를 입력받는 부분은 이미 nextInt()로 받아 버리기 때문에 내 예외처리 보다 내부 예외처리가 먼저 작동해버린다.
그래도 사칙연산자 부분은 작동. 다행이다.
다음은 Calculator.calculate
...
public double calculate (int firstNumber, int secondNumber, char operator) throws Exception{
...
switch(operator) {
...
case '/':
if (secondNumber != 0) {
result = firstNumber / secondNumber;
} else {
throw new BadInputException("0이 아닌 정수");
}
break;
}
...
휴. 다행이다.
App.main에서 빠진 부분에는
public class App {
public static void main(String[] args) {
...
Calulator calulator = new Calulator();
result = calulator.calculate(firstNumber, secondNumber, operator);
...
}
}
다행히도 정상동작한다.
먼저 Calculator의 Queue 필드를 private로 접근 제어
public class Calulator {
private Queue<Double> numQ = new LinkedList<Double>();
...
public Queue<Double> getNumQ() {
return numQ;
}
public void setNumQ(double result) {
this.numQ.add(result);
}
App.main() 에서의 수정.
...
Calculator calculator = new Calculator();
result = calculator.calculate(firstNumber, secondNumber, operator);
calculator.setNumQ(result); // 저장을 setter 메소드로 대체
...
...
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
if(sc.nextLine().equals("inquiry")) {
System.out.println("저장된 연산결과를 불러옵니다.");
Queue<Double> numQ = calculator.getNumQ(); // 저장목록 Queue를 getter 메소드로 불러옴.
for (double num : numQ) {
System.out.println(num);
}
}
...
먼저 삭제와 동시에 값을 반환하는 Queue.peek() 메소드를 활용. 출력문까지 메소드에 포함시켰다.
Calculator.calculate
public void removeNumQ() {
System.out.println("가장 먼저 저장된 "+ this.numQ.peek() +" 를 삭제합니다.");
}
//이에 따른 App.main에서의 수정
App.main
System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
if(sc.nextLine().equals("remove")) {
calculator.removeNumQ();
}
getter메소드를 활용한 기존 조회 부분을 calculator 클래스로 이동 및 수정.
Calculator.calculate
public void searchNumQ() {
System.out.println("저장된 연산결과를 불러옵니다.");
Queue<Double> numQ = this.getNumQ();
for (double num : numQ) {
System.out.println(num);
}
}
//이에 따른 App.main에서의 수정
App.main
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
if(sc.nextLine().equals("inquiry")) {
calculator.searchNumQ();
}
public class Calculator {
private Queue<Double> numQ;
public Calculator() {
this.numQ = new LinkedList<>();
}
하... 나왔다... 큰 장벽... 이게 맞을까? 싶은 구간에 돌입해벌임..
일단은 뒤적뒤적 해본 결과 이걸로 작성했다.
원넓이 공식이 헷갈렸지만, 일단은 어찌저찌 구현.
static double radius;
// 한번 입력된 반지름 값은 변하지 않음 -> static
final static double PI = 3.14159265358979323846;
//파이 값은 절대로 변하지 않을 상수. -> final static
public double calculateCircleArea(double radius) {
this.radius = radius;
return PI * radius * radius;
}
작성하면서도 이게 맞을까? 왜 굳이 위에서 선언해야하지? 라는 생각이 있지만... 그치만 이렇게 하지 않으면 어떻게 쓰는지 모르겠는걸..
캡슐화 메소드들은 위의 numQ의 것들을 가져왔다.
public Queue<Double> getCircleQ() {
return circleQ;
}
public void setCircleQ(double result) {
this.circleQ.add(result);
}
public void removeCircleQ() {
System.out.println("가장 먼저 저장된 " + this.circleQ.peek() + " 를 삭제합니다.");
}
public void searchCircleQ() {
System.out.println("저장된 연산결과를 불러옵니다.");
for (double num : circleQ) {
System.out.println(num);
}
App.main 메소드에는 사칙연산, 원넓이계산 두가지 경우를 switch..case 문으로 두가지 분기.
System.out.println("사칙연산(1) 원넓이계산(2) : ");
Integer calChoice = sc.nextInt();
switch (calChoice) {
case 1 :
....
case 2 :
....
콘솔창 결과.
사칙연산(1) 원넓이계산(2) :
2
원의 반지름을 입력하세요:
10.4
결과: 339.79466141227203
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
inquiry
저장된 연산결과를 불러옵니다.
339.79466141227203
더 계산하시겠습니까? (exit 입력 시 종료)
사칙연산(1) 원넓이계산(2) :
2
원의 반지름을 입력하세요:
12
결과: 452.3893421169302
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
더 계산하시겠습니까? (exit 입력 시 종료)
사칙연산(1) 원넓이계산(2) :
1
첫 번째 숫자를 입력하세요:
2
사칙연산 기호를 입력하세요:
+
두 번째 숫자를 입력하세요:
15
결과: 17.0
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
inquiry
저장된 연산결과를 불러옵니다.
17.0
더 계산하시겠습니까? (exit 입력 시 종료)
사칙연산(1) 원넓이계산(2) :
2
원의 반지름을 입력하세요:
116
결과: 42273.27074670426
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
inquiry
저장된 연산결과를 불러옵니다.
339.79466141227203
452.3893421169302
42273.27074670426
더 계산하시겠습니까? (exit 입력 시 종료)
exit
계산기를 종료합니다.
종료 코드 0(으)로 완료된 프로세스
오 되는데?
만들긴 했는데, 본래 Calculator 안에서 선언되어 있던 각각의 메소드들을 옮겨봤다.
CircleCalculator
import java.util.LinkedList;
import java.util.Queue;
public class CircleCalculator extends Calculator {
private Queue<Double> circleQ;
static double radius;
final static double PI = 3.14159265358979323846;
public CircleCalculator() {
this.circleQ = new LinkedList<>();
}
public double calculate(double radius) {
this.radius = radius;
result = PI * radius * radius;
return result;
}
public Queue<Double> getCircleQ() {
return circleQ;
}
public void setCircleQ(double result) {
this.circleQ.add(result);
}
public void removeCircleQ() {
System.out.println("가장 먼저 저장된 " + this.circleQ.peek() + " 를 삭제합니다.");
}
public void searchCircleQ() {
System.out.println("저장된 연산결과를 불러옵니다.");
for (double num : circleQ) {
System.out.println(num);
}
}
}
ArithmeticCalculator
public class ArithmeticCalculator extends Calculator {
private Queue<Double> numQ;
public ArithmeticCalculator() {
this.numQ = new LinkedList<>();
}
public double calculate(int firstNumber, int secondNumber, char operator) throws Exception {
// 연산자에 따른 연산방법
switch (operator) {
case '+':
result = firstNumber + secondNumber;
break;
case '-':
result = firstNumber - secondNumber;
break;
case '*':
result = firstNumber * secondNumber;
break;
case '/':
if (secondNumber != 0) {
result = firstNumber / secondNumber;
} else {
throw new BadInputException("0이 아닌 정수");
}
break;
}
// 연산방법 제어 끝
return result;
}
public Queue<Double> getNumQ() {
return numQ;
}
public void setNumQ(double result) {
this.numQ.add(result);
}
public void removeNumQ() {
System.out.println("가장 먼저 저장된 " + this.numQ.peek() + " 를 삭제합니다.");
}
public void searchNumQ() {
System.out.println("저장된 연산결과를 불러옵니다.");
for (double num : numQ) {
System.out.println(num);
}
}
}
본래의 Calculator 클래스를 상속받아야 해서 상속문을 작성하긴 했는데 자비없이 그어지는 빨간 밑줄.
Calculator 클래스는 이렇게 추상 클래스로 긴급수정에 들어갔지만 여전히 남아있다.
public abstract class Calculator {
static double result;
public abstract double calculate();
}
큰일이다. 진짜 너무 막혔다. 전혀 모르겠으니 Git 연동도 할겸 처음부터 시작하는 마음으로 해보자.
일단 class 별로 나눴던 메서드들을 다시 Calculator로 모으고 삭제해보자.
추상화는 시기상조라고 생각. 일단 사칙연산, 원넓이계산 두가지 클래스만 구현에 성공했다.
// ArithmeticCalculator.class
public class ArithmeticCalculator {
public double operate(double firstNumber, double secondNumber, char operator) throws Exception {
// 연산자에 따른 연산방법
switch (operator) {
case '+':
return firstNumber + secondNumber;
case '-':
return firstNumber - secondNumber;
case '*':
return firstNumber * secondNumber;
case '/':
if (secondNumber != 0) {
return firstNumber / secondNumber;
} else {
throw new BadInputException("0이 아닌 정수");
}
default: throw new BadInputException("사칙연산자");
}
// 연산방법 제어 끝
}
// CircleCalculator.class
public class CircleCalculator {
// 원넓이
static final double PI = 3.14159265358979323846;
public double operate(double radius) {
return PI * radius * radius;
}
}
이 두가지를 구현하면서 Calculator.class의 연산부분을 뺐고, 두가지 클래스를 불러오고 메소드를 추가했다.
public class Calculator {
...
private final ArithmeticCalculator arithCalc = new ArithmeticCalculator();
private final CircleCalculator circleCalc = new CircleCalculator();
...
public double arithC (double a, double b, char c) throws Exception {
return this.arithCalc.operate(a, b, c);
}
public double circleC (double a) {
return this.circleCalc.operate(a);
}
...
}
결과는 정상작동했다ㅠㅠㅠㅠㅠㅠ
두가지도 힘들었는데 이제는 4가지로 분리해야한다.
public class AddOperator {
public double operate(double a, double b) {
return a + b;
}
}
public class SubtractOperator {
public double operate(double a, double b) {
return a - b;
}
}
public class MultiplyOperator {
public double operate(double a, double b) {
return a * b;
}
}
public class DivideOperator {
public double operate(double a, double b) throws Exception {
if (b == 0) {
throw new BadInputException("0이 아닌 정수");
}
return a / b;
}
}
하지만 일단은 이렇게 했더니 쉬운것 같기도 하고, 그래서 걱정되기도 하고 그렇다.
이에 따라 ArithmeticCalculator.class도 수정.
public class ArithmeticCalculator {
private final AddOperator add = new AddOperator();
private final SubtractOperator sub = new SubtractOperator();
private final MultiplyOperator mul = new MultiplyOperator();
private final DivideOperator div = new DivideOperator();
public double operate(double firstNumber, double secondNumber, char operator) throws Exception {
// 연산자에 따른 연산방법
switch (operator) {
case '+':
return add.operate(firstNumber, secondNumber);
case '-':
return sub.operate(firstNumber, secondNumber);
case '*':
return mul.operate(firstNumber, secondNumber);
case '/':
return div.operate(firstNumber, secondNumber);
default: throw new BadInputException("사칙연산자");
}
// 연산방법 제어 끝
}
}
일단 결과는 다행스럽게도 정상작동.
나머지 연산 추가는 간단하니 생략, 문제는 객체지향원칙에 따른 코드수정인데, 오늘은 여기까지 하고 새벽에 이어서 고민을 해봐야겠다.